lazer pointer wao

feat: send mouse position from server to clients

ptr.pet 6c582687 1639a2c6

verified
Changed files
+409 -96
src
+263 -34
Cargo.lock
···
"anyhow",
"bincode",
"console_error_panic_hook",
"fastrand",
"futures-util",
"quanta",
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
dependencies = [
-
"objc2",
]
[[package]]
···
]
[[package]]
name = "core-graphics-types"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "dlib"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
name = "num_enum"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
name = "objc2-app-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"bitflags 2.9.3",
"block2",
"libc",
-
"objc2",
"objc2-core-data",
"objc2-core-image",
-
"objc2-foundation",
"objc2-quartz-core",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"bitflags 2.9.3",
"block2",
-
"objc2",
"objc2-core-location",
-
"objc2-foundation",
]
[[package]]
···
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [
"block2",
-
"objc2",
-
"objc2-foundation",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
-
"objc2",
-
"objc2-foundation",
]
[[package]]
···
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [
"block2",
-
"objc2",
-
"objc2-foundation",
"objc2-metal",
]
···
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
dependencies = [
"block2",
-
"objc2",
"objc2-contacts",
-
"objc2-foundation",
]
[[package]]
···
"block2",
"dispatch",
"libc",
-
"objc2",
]
[[package]]
···
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
dependencies = [
"block2",
-
"objc2",
-
"objc2-app-kit",
-
"objc2-foundation",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
-
"objc2",
-
"objc2-foundation",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
-
"objc2",
-
"objc2-foundation",
"objc2-metal",
]
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
dependencies = [
-
"objc2",
-
"objc2-foundation",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
-
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-image",
"objc2-core-location",
-
"objc2-foundation",
"objc2-link-presentation",
"objc2-quartz-core",
"objc2-symbols",
···
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [
"block2",
-
"objc2",
-
"objc2-foundation",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
-
"objc2",
"objc2-core-location",
-
"objc2-foundation",
]
[[package]]
···
"js-sys",
"log",
"memmap2",
-
"objc2",
-
"objc2-foundation",
"objc2-quartz-core",
"raw-window-handle",
"redox_syscall 0.5.17",
···
]
[[package]]
name = "wayland-protocols-plasma"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"libc",
"memmap2",
"ndk",
-
"objc2",
-
"objc2-app-kit",
-
"objc2-foundation",
"objc2-ui-kit",
"orbclient",
"percent-encoding",
···
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
[[package]]
name = "xkbcommon-dl"
···
"anyhow",
"bincode",
"console_error_panic_hook",
+
"enigo",
"fastrand",
"futures-util",
"quanta",
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
dependencies = [
+
"objc2 0.5.2",
]
[[package]]
···
]
[[package]]
+
name = "core-graphics"
+
version = "0.25.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
+
dependencies = [
+
"bitflags 2.9.3",
+
"core-foundation 0.10.1",
+
"core-graphics-types 0.2.0",
+
"foreign-types",
+
"libc",
+
]
+
+
[[package]]
name = "core-graphics-types"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
+
name = "dispatch2"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
+
dependencies = [
+
"bitflags 2.9.3",
+
"objc2 0.6.2",
+
]
+
+
[[package]]
name = "dlib"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
+
name = "enigo"
+
version = "0.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "71744ff36f35a4276e8827add8102d0e792378c574fd93cb4e1c8e0505f96b7c"
+
dependencies = [
+
"core-foundation 0.10.1",
+
"core-graphics 0.25.0",
+
"foreign-types-shared",
+
"libc",
+
"log",
+
"nom",
+
"objc2 0.6.2",
+
"objc2-app-kit 0.3.1",
+
"objc2-foundation 0.3.1",
+
"tempfile",
+
"wayland-client",
+
"wayland-protocols-misc",
+
"wayland-protocols-wlr",
+
"windows",
+
"x11rb",
+
"xkbcommon",
+
"xkeysym",
+
]
+
+
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "nom"
+
version = "8.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+
dependencies = [
+
"memchr",
+
]
+
+
[[package]]
name = "num_enum"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "objc2"
+
version = "0.6.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc"
+
dependencies = [
+
"objc2-encode",
+
]
+
+
[[package]]
name = "objc2-app-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"bitflags 2.9.3",
"block2",
"libc",
+
"objc2 0.5.2",
"objc2-core-data",
"objc2-core-image",
+
"objc2-foundation 0.2.2",
"objc2-quartz-core",
]
[[package]]
+
name = "objc2-app-kit"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
+
dependencies = [
+
"bitflags 2.9.3",
+
"objc2 0.6.2",
+
"objc2-foundation 0.3.1",
+
]
+
+
[[package]]
name = "objc2-cloud-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"bitflags 2.9.3",
"block2",
+
"objc2 0.5.2",
"objc2-core-location",
+
"objc2-foundation 0.2.2",
]
[[package]]
···
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [
"block2",
+
"objc2 0.5.2",
+
"objc2-foundation 0.2.2",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
+
"objc2 0.5.2",
+
"objc2-foundation 0.2.2",
+
]
+
+
[[package]]
+
name = "objc2-core-foundation"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
+
dependencies = [
+
"bitflags 2.9.3",
+
"dispatch2",
+
"objc2 0.6.2",
]
[[package]]
···
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [
"block2",
+
"objc2 0.5.2",
+
"objc2-foundation 0.2.2",
"objc2-metal",
]
···
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
dependencies = [
"block2",
+
"objc2 0.5.2",
"objc2-contacts",
+
"objc2-foundation 0.2.2",
]
[[package]]
···
"block2",
"dispatch",
"libc",
+
"objc2 0.5.2",
+
]
+
+
[[package]]
+
name = "objc2-foundation"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
+
dependencies = [
+
"bitflags 2.9.3",
+
"objc2 0.6.2",
+
"objc2-core-foundation",
]
[[package]]
···
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
dependencies = [
"block2",
+
"objc2 0.5.2",
+
"objc2-app-kit 0.2.2",
+
"objc2-foundation 0.2.2",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
+
"objc2 0.5.2",
+
"objc2-foundation 0.2.2",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
+
"objc2 0.5.2",
+
"objc2-foundation 0.2.2",
"objc2-metal",
]
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
dependencies = [
+
"objc2 0.5.2",
+
"objc2-foundation 0.2.2",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
+
"objc2 0.5.2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-image",
"objc2-core-location",
+
"objc2-foundation 0.2.2",
"objc2-link-presentation",
"objc2-quartz-core",
"objc2-symbols",
···
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [
"block2",
+
"objc2 0.5.2",
+
"objc2-foundation 0.2.2",
]
[[package]]
···
dependencies = [
"bitflags 2.9.3",
"block2",
+
"objc2 0.5.2",
"objc2-core-location",
+
"objc2-foundation 0.2.2",
]
[[package]]
···
"js-sys",
"log",
"memmap2",
+
"objc2 0.5.2",
+
"objc2-foundation 0.2.2",
"objc2-quartz-core",
"raw-window-handle",
"redox_syscall 0.5.17",
···
]
[[package]]
+
name = "wayland-protocols-misc"
+
version = "0.3.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c"
+
dependencies = [
+
"bitflags 2.9.3",
+
"wayland-backend",
+
"wayland-client",
+
"wayland-protocols",
+
"wayland-scanner",
+
]
+
+
[[package]]
name = "wayland-protocols-plasma"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
+
name = "windows"
+
version = "0.61.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
+
dependencies = [
+
"windows-collections",
+
"windows-core",
+
"windows-future",
+
"windows-link",
+
"windows-numerics",
+
]
+
+
[[package]]
+
name = "windows-collections"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
+
dependencies = [
+
"windows-core",
+
]
+
+
[[package]]
+
name = "windows-core"
+
version = "0.61.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+
dependencies = [
+
"windows-implement",
+
"windows-interface",
+
"windows-link",
+
"windows-result",
+
"windows-strings",
+
]
+
+
[[package]]
+
name = "windows-future"
+
version = "0.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
+
dependencies = [
+
"windows-core",
+
"windows-link",
+
"windows-threading",
+
]
+
+
[[package]]
+
name = "windows-implement"
+
version = "0.60.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+
dependencies = [
+
"proc-macro2",
+
"quote",
+
"syn",
+
]
+
+
[[package]]
+
name = "windows-interface"
+
version = "0.59.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+
dependencies = [
+
"proc-macro2",
+
"quote",
+
"syn",
+
]
+
+
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
+
name = "windows-numerics"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
+
dependencies = [
+
"windows-core",
+
"windows-link",
+
]
+
+
[[package]]
+
name = "windows-result"
+
version = "0.3.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+
dependencies = [
+
"windows-link",
+
]
+
+
[[package]]
+
name = "windows-strings"
+
version = "0.4.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+
dependencies = [
+
"windows-link",
+
]
+
+
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "windows-threading"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
+
dependencies = [
+
"windows-link",
+
]
+
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"libc",
"memmap2",
"ndk",
+
"objc2 0.5.2",
+
"objc2-app-kit 0.2.2",
+
"objc2-foundation 0.2.2",
"objc2-ui-kit",
"orbclient",
"percent-encoding",
···
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
+
+
[[package]]
+
name = "xkbcommon"
+
version = "0.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9"
+
dependencies = [
+
"libc",
+
"memmap2",
+
"xkeysym",
+
]
[[package]]
name = "xkbcommon-dl"
+2
Cargo.toml
···
server = [
"tokio/net",
"futures-util/std",
"dep:tokio-websockets",
]
client = [
···
wasm-bindgen-futures = "0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "sync"] }
ahash = { version = "0.8", default-features = false, features = ["runtime-rng"] }
···
server = [
"tokio/net",
"futures-util/std",
+
"dep:enigo",
"dep:tokio-websockets",
]
client = [
···
wasm-bindgen-futures = "0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+
enigo = { optional = true, version = "0.5.0", features = ["wayland"] }
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "sync"] }
ahash = { version = "0.8", default-features = false, features = ["runtime-rng"] }
+100 -35
src/main.rs
···
window::{Window, WindowAttributes, WindowId},
};
-
use crate::renderer::{Renderer, skia_rgba_to_bgra_u32};
use crate::ws::LaserMessage;
mod renderer;
mod utils;
···
pub struct LaserOverlay {
gfx: Option<Graphics>,
window: WindowHandle,
laser_points: HashMap<(u64, u8), Vec<LaserPoint>, ahash::RandomState>,
-
in_chan: (mpsc::Sender<LaserMessage>, mpsc::Receiver<LaserMessage>),
-
out_tx: mpsc::Sender<LaserMessage>,
last_render: Instant,
last_cleanup: Instant,
clock: Clock,
···
}
impl LaserOverlay {
-
pub fn new() -> (
-
mpsc::Sender<LaserMessage>,
-
mpsc::Receiver<LaserMessage>,
-
Self,
-
) {
let in_chan = mpsc::channel(1024);
let (out_tx, out_rx) = mpsc::channel(512);
···
let this = Self {
gfx: None,
window: WindowHandle::default(),
laser_points: Default::default(),
in_chan,
out_tx,
···
needs_redraw: false,
has_any_points: false,
};
(this.in_chan.0.clone(), out_rx, this)
}
pub fn window_handle(&self) -> WindowHandle {
self.window.clone()
}
···
self.next_line_id = self.next_line_id.wrapping_add(1);
self.needs_redraw = true;
-
let msg = LaserMessage {
x: position.0 as u32,
y: position.1 as u32,
id: self.client_id,
line_id: self.current_line_id,
-
};
let _ = self.in_chan.0.try_send(msg);
let _ = self.out_tx.try_send(msg);
}
fn handle_mouse_move(&mut self, position: (f32, f32)) {
-
if self.mouse_pressed {
-
let dx = position.0 - self.mouse_pos.0;
-
let dy = position.1 - self.mouse_pos.1;
-
let distance = (dx * dx + dy * dy).sqrt();
-
if distance > 3.0 {
-
self.mouse_pos = position;
-
self.needs_redraw = true;
-
let msg = LaserMessage {
-
x: position.0 as u32,
-
y: position.1 as u32,
-
id: self.client_id,
-
line_id: self.current_line_id,
-
};
-
let _ = self.in_chan.0.try_send(msg);
-
let _ = self.out_tx.try_send(msg);
-
}
}
}
···
}
while let Ok(msg) = self.in_chan.1.try_recv() {
-
self.laser_points
-
.entry((msg.id, msg.line_id))
-
.or_default()
-
.push(LaserPoint::new(msg, self.clock.now()));
-
has_any_points = true;
}
self.has_any_points = has_any_points;
}
#[inline(always)]
fn draw_tapering_laser_line(mut pixmap: PixmapMut, points: &[LaserPoint], now: Instant) {
if points.len() < 2 {
···
for points in self.laser_points.values() {
Self::draw_tapering_laser_line(gfx.pixmap.as_mut(), points, self.clock.now());
}
skia_rgba_to_bgra_u32(gfx.pixmap.data(), frame.deref_mut());
gfx.window.pre_present_notify();
···
WindowEvent::CursorMoved { position, .. } => {
let pos = (position.x as f32, position.y as f32);
self.handle_mouse_move(pos);
-
if !self.mouse_pressed {
-
self.mouse_pos = pos;
-
}
}
WindowEvent::Resized(size) if let Some(gfx) = self.gfx.as_mut() => {
gfx.resize(size.width, size.height).unwrap();
···
tokio::spawn({
let window = window.clone();
let tx = _tx.clone();
-
async move { ws::server::listen(3111, window, tx).await.unwrap() }
});
#[cfg(feature = "client")]
{
···
window::{Window, WindowAttributes, WindowId},
};
use crate::ws::LaserMessage;
+
use crate::{
+
renderer::{Renderer, skia_rgba_to_bgra_u32},
+
ws::WsMessage,
+
};
mod renderer;
mod utils;
···
pub struct LaserOverlay {
gfx: Option<Graphics>,
window: WindowHandle,
+
server_mouse_pos: (f32, f32),
laser_points: HashMap<(u64, u8), Vec<LaserPoint>, ahash::RandomState>,
+
in_chan: (mpsc::Sender<WsMessage>, mpsc::Receiver<WsMessage>),
+
out_tx: mpsc::Sender<WsMessage>,
last_render: Instant,
last_cleanup: Instant,
clock: Clock,
···
}
impl LaserOverlay {
+
pub fn new() -> (mpsc::Sender<WsMessage>, mpsc::Receiver<WsMessage>, Self) {
let in_chan = mpsc::channel(1024);
let (out_tx, out_rx) = mpsc::channel(512);
···
let this = Self {
gfx: None,
window: WindowHandle::default(),
+
server_mouse_pos: (0.0, 0.0),
laser_points: Default::default(),
in_chan,
out_tx,
···
needs_redraw: false,
has_any_points: false,
};
+
(this.in_chan.0.clone(), out_rx, this)
}
+
#[cfg(feature = "server")]
+
pub fn start_mouse_listener(send_tx: tokio::sync::broadcast::Sender<(u64, WsMessage)>) {
+
std::thread::spawn({
+
use enigo::{Enigo, Mouse, Settings};
+
+
move || {
+
let enigo = Enigo::new(&Settings::default()).unwrap();
+
loop {
+
let res = enigo.location();
+
let Ok(pos) = res else {
+
eprintln!("failed to get mouse position: {res:?}");
+
continue;
+
};
+
let msg = WsMessage::Mouse(ws::MouseMessage {
+
x: pos.0 as u32,
+
y: pos.1 as u32,
+
});
+
let _ = send_tx.send((0, msg));
+
std::thread::sleep(Duration::from_millis(1000 / 30));
+
}
+
}
+
});
+
}
+
pub fn window_handle(&self) -> WindowHandle {
self.window.clone()
}
···
self.next_line_id = self.next_line_id.wrapping_add(1);
self.needs_redraw = true;
+
let msg = WsMessage::Laser(LaserMessage {
x: position.0 as u32,
y: position.1 as u32,
id: self.client_id,
line_id: self.current_line_id,
+
});
let _ = self.in_chan.0.try_send(msg);
let _ = self.out_tx.try_send(msg);
}
fn handle_mouse_move(&mut self, position: (f32, f32)) {
+
if !self.mouse_pressed {
+
return;
+
}
+
let dx = position.0 - self.mouse_pos.0;
+
let dy = position.1 - self.mouse_pos.1;
+
let distance = (dx * dx + dy * dy).sqrt();
+
+
if distance > 3.0 {
+
self.mouse_pos = position;
+
self.needs_redraw = true;
+
let msg = WsMessage::Laser(LaserMessage {
+
x: position.0 as u32,
+
y: position.1 as u32,
+
id: self.client_id,
+
line_id: self.current_line_id,
+
});
+
let _ = self.in_chan.0.try_send(msg);
+
let _ = self.out_tx.try_send(msg);
}
}
···
}
while let Ok(msg) = self.in_chan.1.try_recv() {
+
match msg {
+
WsMessage::Laser(msg) => {
+
self.laser_points
+
.entry((msg.id, msg.line_id))
+
.or_default()
+
.push(LaserPoint::new(msg, self.clock.now()));
+
has_any_points = true;
+
}
+
WsMessage::Mouse(msg) => {
+
self.server_mouse_pos = (msg.x as f32, msg.y as f32);
+
self.needs_redraw = true;
+
}
+
}
}
self.has_any_points = has_any_points;
}
+
#[cfg(feature = "client")]
+
#[inline(always)]
+
fn draw_server_mouse(mut pixmap: PixmapMut, mouse_pos: (f32, f32)) {
+
let (x, y) = mouse_pos;
+
let radius = 10.0;
+
let color = Color::WHITE;
+
+
let mut pb = PathBuilder::new();
+
pb.push_circle(x, y, radius);
+
+
if let Some(path) = pb.finish() {
+
let mut paint = Paint::default();
+
paint.set_color(color);
+
paint.anti_alias = true;
+
paint.blend_mode = BlendMode::Source;
+
+
let mut stroke = Stroke::default();
+
stroke.width = radius * 2.0;
+
stroke.line_cap = LineCap::Round;
+
stroke.line_join = LineJoin::Round;
+
+
pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
+
}
+
}
+
#[inline(always)]
fn draw_tapering_laser_line(mut pixmap: PixmapMut, points: &[LaserPoint], now: Instant) {
if points.len() < 2 {
···
for points in self.laser_points.values() {
Self::draw_tapering_laser_line(gfx.pixmap.as_mut(), points, self.clock.now());
}
+
#[cfg(feature = "client")]
+
Self::draw_server_mouse(gfx.pixmap.as_mut(), self.server_mouse_pos);
skia_rgba_to_bgra_u32(gfx.pixmap.data(), frame.deref_mut());
gfx.window.pre_present_notify();
···
WindowEvent::CursorMoved { position, .. } => {
let pos = (position.x as f32, position.y as f32);
self.handle_mouse_move(pos);
+
self.mouse_pos = pos;
}
WindowEvent::Resized(size) if let Some(gfx) = self.gfx.as_mut() => {
gfx.resize(size.width, size.height).unwrap();
···
tokio::spawn({
let window = window.clone();
let tx = _tx.clone();
+
async move {
+
let (server, send_tx) = ws::server::listen(3111, window, tx).await.unwrap();
+
LaserOverlay::start_mouse_listener(send_tx);
+
server.await;
+
}
});
#[cfg(feature = "client")]
{
+44 -27
src/ws.rs
···
const BINCODE_CFG: bincode::config::Configuration = bincode::config::standard();
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub struct LaserMessage {
pub x: u32,
pub y: u32,
···
pub line_id: u8,
}
#[cfg(feature = "client")]
pub mod client {
use futures_util::{SinkExt, StreamExt};
···
use crate::{
AppResult, WindowHandle,
-
ws::{BINCODE_CFG, LaserMessage},
};
pub async fn connect(
server_url: &str,
window: WindowHandle,
-
mut overlay_rx: mpsc::Receiver<LaserMessage>,
-
overlay_tx: mpsc::Sender<LaserMessage>,
id: u64,
) -> AppResult<()> {
let (mut tx, mut rx) = tokio_tungstenite_wasm::connect(server_url).await?.split();
···
while let Some(ev) = rx.next().await {
match ev {
Ok(Message::Binary(payload)) => {
-
let Ok((decoded, _)): Result<(LaserMessage, _), _> =
bincode::decode_from_slice(&payload, BINCODE_CFG)
else {
continue;
};
-
// don't send messages from the client we are on
-
if decoded.id == id {
-
continue;
-
}
let _ = overlay_tx.send(decoded).await;
if let Some(window) = window.get() {
window.request_redraw();
···
}
Ok(Message::Close(_)) => {
eprintln!("server closed connection");
break;
}
Err(err) => {
eprintln!("error receiving message: {}", err);
}
_ => {}
}
···
use crate::{
AppResult, WindowHandle,
-
ws::{BINCODE_CFG, LaserMessage},
};
pub async fn listen(
port: u16,
window: WindowHandle,
-
overlay_tx: mpsc::Sender<LaserMessage>,
-
) -> AppResult<impl Future> {
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = TcpListener::bind(&addr).await?;
println!("listening on {}", addr);
let (tx, mut rx) = broadcast::channel(1024);
-
let server_task = tokio::spawn(async move {
-
loop {
-
let conn = match listener.accept().await {
-
Ok((conn, addr)) => {
-
println!("accepted connection from {}", addr);
-
conn
-
}
-
Err(err) => {
-
eprintln!("error accepting connection: {}", err);
-
continue;
-
}
-
};
-
tokio::spawn(handle_server_conn(conn, tx.clone(), tx.subscribe()));
}
});
let overlay_task = tokio::spawn(async move {
···
}
});
-
Ok(futures_util::future::join(server_task, overlay_task))
}
async fn handle_server_conn(
conn: TcpStream,
-
msg_tx: broadcast::Sender<(u64, LaserMessage)>,
-
mut msg_rx: broadcast::Receiver<(u64, LaserMessage)>,
) -> impl Future {
let (_, server) = tokio_websockets::ServerBuilder::new()
.accept(conn)
···
const BINCODE_CFG: bincode::config::Configuration = bincode::config::standard();
#[derive(Debug, Clone, Copy, Encode, Decode)]
+
pub enum WsMessage {
+
Laser(LaserMessage),
+
Mouse(MouseMessage),
+
}
+
+
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub struct LaserMessage {
pub x: u32,
pub y: u32,
···
pub line_id: u8,
}
+
#[derive(Debug, Clone, Copy, Encode, Decode)]
+
pub struct MouseMessage {
+
pub x: u32,
+
pub y: u32,
+
}
+
#[cfg(feature = "client")]
pub mod client {
use futures_util::{SinkExt, StreamExt};
···
use crate::{
AppResult, WindowHandle,
+
ws::{BINCODE_CFG, WsMessage},
};
pub async fn connect(
server_url: &str,
window: WindowHandle,
+
mut overlay_rx: mpsc::Receiver<WsMessage>,
+
overlay_tx: mpsc::Sender<WsMessage>,
id: u64,
) -> AppResult<()> {
let (mut tx, mut rx) = tokio_tungstenite_wasm::connect(server_url).await?.split();
···
while let Some(ev) = rx.next().await {
match ev {
Ok(Message::Binary(payload)) => {
+
let Ok((decoded, _)): Result<(WsMessage, _), _> =
bincode::decode_from_slice(&payload, BINCODE_CFG)
else {
continue;
};
let _ = overlay_tx.send(decoded).await;
if let Some(window) = window.get() {
window.request_redraw();
···
}
Ok(Message::Close(_)) => {
eprintln!("server closed connection");
+
if let Some(window) = window.get() {
+
window.set_title("offline");
+
}
break;
}
Err(err) => {
eprintln!("error receiving message: {}", err);
+
if let Some(window) = window.get() {
+
window.set_title(&format!("error: {}", err));
+
}
}
_ => {}
}
···
use crate::{
AppResult, WindowHandle,
+
ws::{BINCODE_CFG, WsMessage},
};
pub async fn listen(
port: u16,
window: WindowHandle,
+
overlay_tx: mpsc::Sender<WsMessage>,
+
) -> AppResult<(impl Future, broadcast::Sender<(u64, WsMessage)>)> {
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = TcpListener::bind(&addr).await?;
println!("listening on {}", addr);
let (tx, mut rx) = broadcast::channel(1024);
+
let server_task = tokio::spawn({
+
let tx = tx.clone();
+
async move {
+
loop {
+
let conn = match listener.accept().await {
+
Ok((conn, addr)) => {
+
println!("accepted connection from {}", addr);
+
conn
+
}
+
Err(err) => {
+
eprintln!("error accepting connection: {}", err);
+
continue;
+
}
+
};
+
tokio::spawn(handle_server_conn(conn, tx.clone(), tx.subscribe()));
+
}
}
});
let overlay_task = tokio::spawn(async move {
···
}
});
+
Ok((futures_util::future::join(server_task, overlay_task), tx))
}
async fn handle_server_conn(
conn: TcpStream,
+
msg_tx: broadcast::Sender<(u64, WsMessage)>,
+
mut msg_rx: broadcast::Receiver<(u64, WsMessage)>,
) -> impl Future {
let (_, server) = tokio_websockets::ServerBuilder::new()
.accept(conn)