mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-17 14:07:28 +08:00
feat: cursor, linux (#12822)
* feat: cursor, linux Signed-off-by: fufesou <linlong1266@gmail.com> * refact: cursor, text, white background Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
419
Cargo.lock
generated
419
Cargo.lock
generated
@@ -2,6 +2,22 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph"
|
||||
version = "0.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d"
|
||||
dependencies = [
|
||||
"ab_glyph_rasterizer",
|
||||
"owned_ttf_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph_rasterizer"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.22.0"
|
||||
@@ -39,6 +55,19 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy 0.8.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
@@ -96,6 +125,33 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046"
|
||||
dependencies = [
|
||||
"android-properties",
|
||||
"bitflags 2.9.1",
|
||||
"cc",
|
||||
"cesu8",
|
||||
"jni",
|
||||
"jni-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk 0.9.0",
|
||||
"ndk-context",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"num_enum 0.7.2",
|
||||
"thiserror 1.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-properties"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -866,6 +922,32 @@ dependencies = [
|
||||
"system-deps 6.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"log",
|
||||
"polling 3.7.2",
|
||||
"rustix 0.38.34",
|
||||
"slab",
|
||||
"thiserror 1.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||
dependencies = [
|
||||
"calloop",
|
||||
"rustix 0.38.34",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.13"
|
||||
@@ -1584,6 +1666,12 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cursor-icon"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "dart-sys"
|
||||
version = "4.1.5"
|
||||
@@ -2568,7 +2656,7 @@ dependencies = [
|
||||
"nix 0.29.0",
|
||||
"page_size",
|
||||
"smallvec",
|
||||
"zerocopy 0.8.14",
|
||||
"zerocopy 0.8.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3212,7 +3300,7 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ahash 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3987,6 +4075,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"libc",
|
||||
"redox_syscall 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4380,6 +4469,21 @@ dependencies = [
|
||||
"thiserror 1.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"num_enum 0.7.2",
|
||||
"raw-window-handle 0.6.2",
|
||||
"thiserror 1.0.61",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-context"
|
||||
version = "0.1.1"
|
||||
@@ -4404,6 +4508,15 @@ dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.6.0+11769913"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
|
||||
dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-core"
|
||||
version = "0.5.0"
|
||||
@@ -4834,6 +4947,30 @@ dependencies = [
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-contacts"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.2.2"
|
||||
@@ -4858,6 +4995,18 @@ dependencies = [
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-location"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-contacts",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "2.0.0-pre.2"
|
||||
@@ -4886,6 +5035,18 @@ dependencies = [
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-link-presentation"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.2.2"
|
||||
@@ -4911,6 +5072,61 @@ dependencies = [
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-symbols"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
|
||||
dependencies = [
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-ui-kit"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
"objc2-link-presentation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-symbols",
|
||||
"objc2-uniform-type-identifiers",
|
||||
"objc2-user-notifications",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-uniform-type-identifiers"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-user-notifications"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_exception"
|
||||
version = "0.1.2"
|
||||
@@ -5017,6 +5233,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "orbclient"
|
||||
version = "0.3.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43"
|
||||
dependencies = [
|
||||
"libredox",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.4.3"
|
||||
@@ -5087,6 +5312,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
|
||||
dependencies = [
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
@@ -5823,7 +6057,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
"zerocopy 0.8.14",
|
||||
"zerocopy 0.8.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6397,6 +6631,7 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
"windows 0.61.1",
|
||||
"windows-service",
|
||||
"winit",
|
||||
"winreg 0.11.0",
|
||||
"winres",
|
||||
"wol-rs",
|
||||
@@ -6637,6 +6872,19 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sctk-adwaita"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"log",
|
||||
"memmap2",
|
||||
"smithay-client-toolkit",
|
||||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.10.0"
|
||||
@@ -6944,6 +7192,40 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2",
|
||||
"rustix 0.38.34",
|
||||
"thiserror 1.0.61",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-csd-frame",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
|
||||
dependencies = [
|
||||
"serde 1.0.203",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.19"
|
||||
@@ -8373,12 +8655,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.42"
|
||||
version = "0.4.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
@@ -8441,6 +8724,28 @@ dependencies = [
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-csd-frame"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cursor-icon",
|
||||
"wayland-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95"
|
||||
dependencies = [
|
||||
"rustix 0.38.34",
|
||||
"wayland-client",
|
||||
"xcursor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.3"
|
||||
@@ -8453,6 +8758,19 @@ dependencies = [
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-plasma"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79f2d57c7fcc6ab4d602adba364bf59a5c24de57bd194486bf9b8360e06bfc4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.3"
|
||||
@@ -8491,9 +8809,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.69"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -9277,6 +9595,58 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winit"
|
||||
version = "0.30.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.5.1",
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"cfg_aliases 0.2.1",
|
||||
"concurrent-queue",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics 0.23.2",
|
||||
"cursor-icon",
|
||||
"dpi",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"memmap2",
|
||||
"ndk 0.9.0",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
"orbclient",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"raw-window-handle 0.6.2",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix 0.38.34",
|
||||
"sctk-adwaita",
|
||||
"smithay-client-toolkit",
|
||||
"smol_str",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-plasma",
|
||||
"web-sys",
|
||||
"web-time",
|
||||
"windows-sys 0.52.0",
|
||||
"x11-dl",
|
||||
"x11rb 0.13.1",
|
||||
"xkbcommon-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
@@ -9459,6 +9829,12 @@ dependencies = [
|
||||
"rustix 0.38.34",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
||||
|
||||
[[package]]
|
||||
name = "xdg-home"
|
||||
version = "1.2.0"
|
||||
@@ -9469,6 +9845,25 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon-dl"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"dlib",
|
||||
"log",
|
||||
"once_cell",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkeysym"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "3.15.2"
|
||||
@@ -9547,11 +9942,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.14"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
|
||||
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.14",
|
||||
"zerocopy-derive 0.8.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9567,9 +9962,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.14"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
|
||||
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.93",
|
||||
"quote 1.0.36",
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -134,11 +134,6 @@ impersonate_system = { git = "https://github.com/rustdesk-org/impersonate-system
|
||||
shared_memory = "0.12"
|
||||
tauri-winrt-notification = "0.1"
|
||||
runas = "1.2"
|
||||
tiny-skia = "0.11"
|
||||
softbuffer = "0.4"
|
||||
fontdb = "0.23"
|
||||
bytemuck = "1.23"
|
||||
ttf-parser = "0.25"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2"
|
||||
@@ -164,6 +159,11 @@ keepawake = { git = "https://github.com/rustdesk-org/keepawake-rs" }
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
|
||||
wallpaper = { git = "https://github.com/rustdesk-org/wallpaper.rs" }
|
||||
tiny-skia = "0.11"
|
||||
softbuffer = "0.4"
|
||||
fontdb = "0.23"
|
||||
bytemuck = "1.23"
|
||||
ttf-parser = "0.25"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
||||
# https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support
|
||||
@@ -190,6 +190,7 @@ nix = { version = "0.29", features = ["term", "process"]}
|
||||
gtk = "0.18"
|
||||
termios = "0.3"
|
||||
terminfo = "0.8"
|
||||
winit = "0.30"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.13"
|
||||
|
||||
@@ -1593,8 +1593,8 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
inputSource(),
|
||||
Divider(),
|
||||
viewMode(),
|
||||
if (pi.platform == kPeerPlatformWindows ||
|
||||
pi.platform == kPeerPlatformMacOS)
|
||||
if ([kPeerPlatformWindows, kPeerPlatformMacOS, kPeerPlatformLinux]
|
||||
.contains(pi.platform))
|
||||
showMyCursor(),
|
||||
Divider(),
|
||||
...toolbarToggles(),
|
||||
|
||||
@@ -575,7 +575,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--whiteboard" {
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::whiteboard::run();
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ pub enum Data {
|
||||
#[cfg(target_os = "windows")]
|
||||
PortForwardSessionCount(Option<usize>),
|
||||
SocksWs(Option<Box<(Option<config::Socks5Server>, String)>>),
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Whiteboard((String, crate::whiteboard::CustomEvent)),
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ pub mod plugin;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod tray;
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod whiteboard;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
|
||||
@@ -3699,24 +3699,35 @@ impl Connection {
|
||||
self.update_terminal_persistence(q == BoolOption::Yes).await;
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(q) = o.show_my_cursor.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
use crate::whiteboard;
|
||||
self.show_my_cursor = q == BoolOption::Yes;
|
||||
#[cfg(target_os = "windows")]
|
||||
let is_win10_or_greater = crate::platform::windows::is_win_10_or_greater();
|
||||
let is_lower_win10 = !crate::platform::windows::is_win_10_or_greater();
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let is_win10_or_greater = false;
|
||||
let is_lower_win10 = false;
|
||||
#[cfg(target_os = "linux")]
|
||||
let is_wayland = !crate::platform::linux::is_x11();
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let is_wayland = false;
|
||||
let not_support_msg = if is_lower_win10 {
|
||||
"Windows 10 or greater is required."
|
||||
} else if is_wayland {
|
||||
"This feature is not supported on Wayland, please switch to X11."
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if q == BoolOption::Yes {
|
||||
if !cfg!(target_os = "windows") || is_win10_or_greater {
|
||||
if not_support_msg.is_empty() {
|
||||
whiteboard::register_whiteboard(whiteboard::get_key_cursor(self.inner.id));
|
||||
} else {
|
||||
let mut msg_out = Message::new();
|
||||
let res = MessageBox {
|
||||
msgtype: "nook-nocancel-hasclose".to_owned(),
|
||||
title: "Show my cursor".to_owned(),
|
||||
text: "Windows 10 or greater is required.".to_owned(),
|
||||
text: not_support_msg.to_owned(),
|
||||
link: "".to_owned(),
|
||||
..Default::default()
|
||||
};
|
||||
@@ -3724,7 +3735,7 @@ impl Connection {
|
||||
self.send(msg_out).await;
|
||||
}
|
||||
} else {
|
||||
if !cfg!(target_os = "windows") || is_win10_or_greater {
|
||||
if not_support_msg.is_empty() {
|
||||
whiteboard::unregister_whiteboard(whiteboard::get_key_cursor(
|
||||
self.inner.id,
|
||||
));
|
||||
@@ -4884,7 +4895,7 @@ mod raii {
|
||||
scrap::wayland::pipewire::try_close_session();
|
||||
}
|
||||
Self::check_wake_lock();
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
use crate::whiteboard;
|
||||
whiteboard::unregister_whiteboard(whiteboard::get_key_cursor(self.0));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use super::rdp_input::client::{RdpInputKeyboard, RdpInputMouse};
|
||||
use super::*;
|
||||
use crate::input::*;
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::whiteboard;
|
||||
#[cfg(target_os = "macos")]
|
||||
use dispatch::Queue;
|
||||
@@ -1000,7 +1000,7 @@ pub fn handle_mouse_(
|
||||
if simulate {
|
||||
handle_mouse_simulation_(evt, conn);
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if _show_cursor {
|
||||
handle_mouse_show_cursor_(evt, conn, _username, _argb);
|
||||
}
|
||||
@@ -1149,7 +1149,7 @@ pub fn handle_mouse_simulation_(evt: &MouseEvent, conn: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn handle_mouse_show_cursor_(evt: &MouseEvent, conn: i32, username: String, argb: u32) {
|
||||
let buttons = evt.mask >> 3;
|
||||
let evt_type = evt.mask & 0x7;
|
||||
|
||||
426
src/whiteboard/linux.rs
Normal file
426
src/whiteboard/linux.rs
Normal file
@@ -0,0 +1,426 @@
|
||||
use super::{
|
||||
server::{Ripple, EVENT_PROXY},
|
||||
win_linux::{create_font_face, draw_text},
|
||||
Cursor, CustomEvent,
|
||||
};
|
||||
use hbb_common::{bail, log, tokio::sync::mpsc::unbounded_channel, ResultType};
|
||||
use softbuffer::{Context, Surface};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{c_int, c_short, c_ulong, c_ushort},
|
||||
num::NonZeroU32,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
use tiny_skia::{Color, FillRule, Paint, PathBuilder, PixmapMut, Stroke, Transform};
|
||||
use ttf_parser::Face;
|
||||
use winit::raw_window_handle::{
|
||||
DisplayHandle, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle,
|
||||
};
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::WindowEvent,
|
||||
event_loop::{ActiveEventLoop, EventLoop},
|
||||
platform::x11::{WindowAttributesExtX11, WindowType},
|
||||
window::{Window, WindowId, WindowLevel},
|
||||
};
|
||||
|
||||
enum _XDisplay {}
|
||||
type Display = _XDisplay;
|
||||
|
||||
type XID = c_ulong;
|
||||
type XserverRegion = XID;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct XRectangle {
|
||||
pub x: c_short,
|
||||
pub y: c_short,
|
||||
pub width: c_ushort,
|
||||
pub height: c_ushort,
|
||||
}
|
||||
|
||||
#[link(name = "Xfixes")]
|
||||
extern "C" {
|
||||
fn XFixesCreateRegion(
|
||||
dpy: *mut Display,
|
||||
rectangles: *mut XRectangle,
|
||||
nrectangles: c_int,
|
||||
) -> XserverRegion;
|
||||
fn XFixesDestroyRegion(dpy: *mut Display, region: XserverRegion) -> ();
|
||||
fn XFixesSetWindowShapeRegion(
|
||||
dpy: *mut Display,
|
||||
win: XID,
|
||||
shape_kind: c_int,
|
||||
x_off: c_int,
|
||||
y_off: c_int,
|
||||
region: XserverRegion,
|
||||
) -> ();
|
||||
}
|
||||
|
||||
const SHAPE_INPUT: std::ffi::c_int = 2;
|
||||
|
||||
pub fn run() {
|
||||
let event_loop = match EventLoop::<(String, CustomEvent)>::with_user_event().build() {
|
||||
Ok(el) => el,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create event loop: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let event_loop_proxy = event_loop.create_proxy();
|
||||
EVENT_PROXY.write().unwrap().replace(event_loop_proxy);
|
||||
|
||||
let (tx_exit, rx_exit) = unbounded_channel();
|
||||
std::thread::spawn(move || {
|
||||
super::server::start_ipc(rx_exit);
|
||||
});
|
||||
|
||||
let mut app = match WhiteboardApplication::new(&event_loop) {
|
||||
Ok(app) => app,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create whiteboard application: {}", e);
|
||||
tx_exit.send(()).ok();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = event_loop.run_app(&mut app) {
|
||||
log::error!("Failed to run app: {}", e);
|
||||
tx_exit.send(()).ok();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct WindowState {
|
||||
window: Arc<Window>,
|
||||
// NOTE: This surface must be dropped before the `Window`.
|
||||
surface: Surface<DisplayHandle<'static>, Arc<Window>>,
|
||||
ripples: Vec<Ripple>,
|
||||
last_cursors: HashMap<String, Cursor>,
|
||||
}
|
||||
|
||||
struct WhiteboardApplication {
|
||||
windows: Vec<WindowState>,
|
||||
// Drawing context.
|
||||
//
|
||||
// With OpenGL it could be EGLDisplay.
|
||||
context: Option<Context<DisplayHandle<'static>>>,
|
||||
face: Option<Face<'static>>,
|
||||
close_requested: bool,
|
||||
}
|
||||
|
||||
impl WhiteboardApplication {
|
||||
fn new<T>(event_loop: &EventLoop<T>) -> ResultType<Self> {
|
||||
// https://github.com/rust-windowing/winit/blob/f6893a4390dfe6118ce4b33458d458fd3efd3025/examples/window.rs#L91
|
||||
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
|
||||
let context = match Context::new(unsafe {
|
||||
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
|
||||
event_loop.display_handle()?,
|
||||
)
|
||||
}) {
|
||||
Ok(ctx) => Some(ctx),
|
||||
Err(e) => {
|
||||
bail!("Failed to create context: {}", e);
|
||||
}
|
||||
};
|
||||
let face = match create_font_face() {
|
||||
Ok(face) => Some(face),
|
||||
Err(err) => {
|
||||
log::error!("Failed to create font face: {}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
windows: Vec::new(),
|
||||
context,
|
||||
face,
|
||||
close_requested: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler<(String, CustomEvent)> for WhiteboardApplication {
|
||||
fn user_event(&mut self, _event_loop: &ActiveEventLoop, (k, evt): (String, CustomEvent)) {
|
||||
match evt {
|
||||
CustomEvent::Cursor(cursor) => {
|
||||
if let Some(state) = self.windows.first_mut() {
|
||||
if cursor.btns != 0 {
|
||||
state.ripples.push(Ripple {
|
||||
x: cursor.x,
|
||||
y: cursor.y,
|
||||
start_time: Instant::now(),
|
||||
});
|
||||
}
|
||||
state.last_cursors.insert(k, cursor);
|
||||
state.window.request_redraw();
|
||||
}
|
||||
}
|
||||
CustomEvent::Exit => {
|
||||
self.close_requested = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let (x, y, w, h) = match super::server::get_displays_rect() {
|
||||
Ok(r) => r,
|
||||
Err(err) => {
|
||||
log::error!("Failed to get displays rect: {}", err);
|
||||
self.close_requested = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let window_attributes = Window::default_attributes()
|
||||
.with_title("RustDesk whiteboard")
|
||||
.with_inner_size(PhysicalSize::new(w, h))
|
||||
.with_position(PhysicalPosition::new(x, y))
|
||||
.with_decorations(false)
|
||||
.with_transparent(true)
|
||||
.with_window_level(WindowLevel::AlwaysOnTop)
|
||||
.with_x11_window_type(vec![WindowType::Dock])
|
||||
.with_override_redirect(true);
|
||||
|
||||
let window = match event_loop.create_window(window_attributes) {
|
||||
Ok(window) => Arc::new(window),
|
||||
Err(e) => {
|
||||
log::error!("Failed to create window: {}", e);
|
||||
self.close_requested = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let display = match window.display_handle() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::error!("Failed to get display handle: {}", e);
|
||||
self.close_requested = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let rwh = match window.window_handle() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
log::error!("Failed to get window handle: {}", e);
|
||||
self.close_requested = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Both the following block and `window.set_cursor_hittest(false)` in `draw()` are necessary to ensure cursor events are properly passed through the window.
|
||||
// These issues may be related to winit X11 handling.
|
||||
// https://github.com/rust-windowing/winit/issues/3509
|
||||
// https://github.com/rust-windowing/winit/issues/4120
|
||||
// If either block is removed, cursor events may not be passed through as expected.
|
||||
// If you update winit, please revisit this workaround.
|
||||
match (rwh.as_raw(), display.as_raw()) {
|
||||
(RawWindowHandle::Xlib(xlib_window), RawDisplayHandle::Xlib(xlib_display)) => {
|
||||
unsafe {
|
||||
let xwindow = xlib_window.window;
|
||||
if let Some(display_ptr) = xlib_display.display {
|
||||
let xdisplay = display_ptr.as_ptr() as *mut Display;
|
||||
// Mouse event passthrough
|
||||
let empty_region = XFixesCreateRegion(xdisplay, std::ptr::null_mut(), 0);
|
||||
if empty_region == 0 {
|
||||
log::error!("XFixesCreateRegion failed: returned null region");
|
||||
} else {
|
||||
XFixesSetWindowShapeRegion(
|
||||
xdisplay,
|
||||
xwindow,
|
||||
SHAPE_INPUT,
|
||||
0,
|
||||
0,
|
||||
empty_region,
|
||||
);
|
||||
XFixesDestroyRegion(xdisplay, empty_region);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unsupported windowing system for shape extension");
|
||||
self.close_requested = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(ctx) = self.context.as_ref() else {
|
||||
// unreachable
|
||||
self.close_requested = true;
|
||||
return;
|
||||
};
|
||||
|
||||
let surface = match Surface::new(ctx, window.clone()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create surface: {}", e);
|
||||
self.close_requested = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let state = WindowState {
|
||||
window,
|
||||
surface,
|
||||
ripples: Vec::new(),
|
||||
last_cursors: HashMap::new(),
|
||||
};
|
||||
|
||||
self.windows.push(state);
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
_event_loop: &ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
self.close_requested = true;
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
let Some(state) = self.windows.iter_mut().find(|w| w.window.id() == window_id)
|
||||
else {
|
||||
log::error!("No window found for id: {:?}", window_id);
|
||||
return;
|
||||
};
|
||||
if let Err(err) = state.draw(&self.face) {
|
||||
log::error!("Failed to draw window: {}", err);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||
if !self.close_requested {
|
||||
for state in self.windows.iter() {
|
||||
state.window.request_redraw();
|
||||
}
|
||||
} else {
|
||||
event_loop.exit();
|
||||
}
|
||||
}
|
||||
|
||||
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
|
||||
// We must drop the context here.
|
||||
self.context = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
fn draw(&mut self, face: &Option<Face<'static>>) -> ResultType<()> {
|
||||
let (width, height) = {
|
||||
let size = self.window.inner_size();
|
||||
(size.width, size.height)
|
||||
};
|
||||
|
||||
let (Some(width), Some(height)) = (NonZeroU32::new(width), NonZeroU32::new(height)) else {
|
||||
bail!("Invalid window size, {width}x{height}")
|
||||
};
|
||||
if let Err(e) = self.surface.resize(width, height) {
|
||||
bail!("Failed to resize surface: {}", e);
|
||||
}
|
||||
|
||||
let mut buffer = match self.surface.buffer_mut() {
|
||||
Ok(buf) => buf,
|
||||
Err(e) => {
|
||||
bail!("Failed to get buffer: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
let Some(mut pixmap) = PixmapMut::from_bytes(
|
||||
bytemuck::cast_slice_mut(&mut buffer),
|
||||
width.get(),
|
||||
height.get(),
|
||||
) else {
|
||||
bail!("Failed to create pixmap from buffer");
|
||||
};
|
||||
pixmap.fill(Color::TRANSPARENT);
|
||||
|
||||
Ripple::retain_active(&mut self.ripples);
|
||||
for ripple in &self.ripples {
|
||||
let (radius, alpha) = ripple.get_radius_alpha();
|
||||
|
||||
let mut ripple_paint = Paint::default();
|
||||
// Note: The real color is bgra here.
|
||||
ripple_paint.set_color_rgba8(64, 64, 255, (alpha * 128.0) as u8);
|
||||
ripple_paint.anti_alias = true;
|
||||
|
||||
let mut ripple_pb = PathBuilder::new();
|
||||
ripple_pb.push_circle(ripple.x, ripple.y, radius);
|
||||
if let Some(path) = ripple_pb.finish() {
|
||||
pixmap.fill_path(
|
||||
&path,
|
||||
&ripple_paint,
|
||||
FillRule::Winding,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for cursor in self.last_cursors.values() {
|
||||
let (x, y) = (cursor.x, cursor.y);
|
||||
let size = 1.5f32;
|
||||
|
||||
let mut pb = PathBuilder::new();
|
||||
pb.move_to(x, y);
|
||||
pb.line_to(x, y + 16.0 * size);
|
||||
pb.line_to(x + 4.0 * size, y + 13.0 * size);
|
||||
pb.line_to(x + 7.0 * size, y + 20.0 * size);
|
||||
pb.line_to(x + 9.0 * size, y + 19.0 * size);
|
||||
pb.line_to(x + 6.0 * size, y + 12.0 * size);
|
||||
pb.line_to(x + 11.0 * size, y + 12.0 * size);
|
||||
pb.close();
|
||||
|
||||
if let Some(path) = pb.finish() {
|
||||
let mut arrow_paint = Paint::default();
|
||||
let rgba = super::argb_to_rgba(cursor.argb);
|
||||
arrow_paint.set_color_rgba8(rgba.2, rgba.1, rgba.0, rgba.3);
|
||||
arrow_paint.anti_alias = true;
|
||||
pixmap.fill_path(
|
||||
&path,
|
||||
&arrow_paint,
|
||||
FillRule::Winding,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut black_paint = Paint::default();
|
||||
black_paint.set_color_rgba8(0, 0, 0, 255);
|
||||
black_paint.anti_alias = true;
|
||||
let mut stroke = Stroke::default();
|
||||
stroke.width = 1.0f32;
|
||||
pixmap.stroke_path(&path, &black_paint, &stroke, Transform::identity(), None);
|
||||
|
||||
face.as_ref().map(|face| {
|
||||
draw_text(
|
||||
&mut pixmap,
|
||||
face,
|
||||
&cursor.text,
|
||||
x + 24.0 * size,
|
||||
y + 24.0 * size,
|
||||
&arrow_paint,
|
||||
14.0f32,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.window.pre_present_notify();
|
||||
|
||||
if let Err(e) = buffer.present() {
|
||||
log::error!("Failed to present buffer: {}", e);
|
||||
}
|
||||
|
||||
self.window.set_cursor_hittest(false).ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
use super::{server::EVENT_PROXY, Cursor, CustomEvent};
|
||||
use super::{server::EVENT_PROXY, Cursor, CustomEvent, Ripple};
|
||||
use core_graphics::context::CGContextRef;
|
||||
use foreign_types::ForeignTypeRef;
|
||||
use hbb_common::{bail, log, ResultType};
|
||||
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
|
||||
use piet::{kurbo::BezPath, FontFamily, RenderContext, Text, TextLayoutBuilder};
|
||||
use piet::{
|
||||
kurbo::{BezPath, Point},
|
||||
FontFamily, RenderContext, Text, TextLayout, TextLayoutBuilder,
|
||||
};
|
||||
use piet_coregraphics::{CoreGraphicsContext, CoreGraphicsTextLayout};
|
||||
use std::{collections::HashMap, sync::Arc, time::Instant};
|
||||
use tao::{
|
||||
@@ -27,12 +30,6 @@ struct WindowState {
|
||||
display_origin: (f64, f64),
|
||||
}
|
||||
|
||||
struct Ripple {
|
||||
x: f64,
|
||||
y: f64,
|
||||
start_time: Instant,
|
||||
}
|
||||
|
||||
struct CursorInfo {
|
||||
window_id: WindowId,
|
||||
text_key: (String, u32),
|
||||
@@ -144,23 +141,14 @@ fn draw_cursors(
|
||||
context.clear(None, piet::Color::TRANSPARENT);
|
||||
|
||||
if let Some(ripples) = window_ripples.get_mut(&window_id) {
|
||||
let ripple_duration = std::time::Duration::from_millis(500);
|
||||
ripples.retain_mut(|ripple| {
|
||||
let elapsed = ripple.start_time.elapsed();
|
||||
let progress =
|
||||
elapsed.as_secs_f64() / ripple_duration.as_secs_f64();
|
||||
let radius = 25.0 * progress;
|
||||
let alpha = 1.0 - progress;
|
||||
if alpha > 0.0 {
|
||||
let color = piet::Color::rgba(1.0, 0.5, 0.5, alpha);
|
||||
let circle =
|
||||
piet::kurbo::Circle::new((ripple.x, ripple.y), radius);
|
||||
context.stroke(circle, &color, 2.0);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
Ripple::retain_active(ripples);
|
||||
for ripple in ripples.iter() {
|
||||
let (radius, alpha) = ripple.get_radius_alpha();
|
||||
let color = piet::Color::rgba(1.0, 0.25, 0.25, alpha * 0.5);
|
||||
let circle =
|
||||
piet::kurbo::Circle::new((ripple.x, ripple.y), radius);
|
||||
context.stroke(circle, &color, 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
for info in last_cursors.values() {
|
||||
@@ -181,26 +169,34 @@ fn draw_cursors(
|
||||
pb.line_to((x + 6.0 * size, y + 12.0 * size));
|
||||
pb.line_to((x + 11.0 * size, y + 12.0 * size));
|
||||
|
||||
let color = piet::Color::rgba8(
|
||||
(cursor.argb >> 16 & 0xFF) as u8,
|
||||
(cursor.argb >> 8 & 0xFF) as u8,
|
||||
(cursor.argb & 0xFF) as u8,
|
||||
(cursor.argb >> 24 & 0xFF) as u8,
|
||||
);
|
||||
let rgba = super::argb_to_rgba(cursor.argb);
|
||||
let color = piet::Color::rgba8(rgba.0, rgba.1, rgba.2, rgba.3);
|
||||
context.fill(pb, &color);
|
||||
|
||||
let pos =
|
||||
(x + CURSOR_TEXT_OFFSET * size, y + CURSOR_TEXT_OFFSET * size);
|
||||
let get_rounded_rect = |layout: &CoreGraphicsTextLayout| {
|
||||
let text_pos = Point::new(pos.0, pos.1);
|
||||
let padded_bounds = (layout.image_bounds()
|
||||
+ text_pos.to_vec2())
|
||||
.inflate(3.0, 3.0);
|
||||
padded_bounds.to_rounded_rect(5.0)
|
||||
};
|
||||
|
||||
if let Some(layout) = map_cursor_text.get(&info.text_key) {
|
||||
context.fill(get_rounded_rect(layout), &piet::Color::WHITE);
|
||||
context.draw_text(layout, pos);
|
||||
} else {
|
||||
let text = context.text();
|
||||
let color = piet::Color::rgba8(0, 0, 0, 255);
|
||||
if let Ok(layout) = text
|
||||
.new_text_layout(cursor.text.clone())
|
||||
.font(FontFamily::SYSTEM_UI, CURSOR_TEXT_FONT_SIZE)
|
||||
.text_color(color)
|
||||
.build()
|
||||
{
|
||||
context
|
||||
.fill(get_rounded_rect(&layout), &piet::Color::WHITE);
|
||||
context.draw_text(&layout, pos);
|
||||
map_cursor_text.insert(info.text_key.clone(), layout);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,12 @@ mod server;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
mod win_linux;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows::create_event_loop;
|
||||
|
||||
@@ -1,29 +1,43 @@
|
||||
use super::{create_event_loop, CustomEvent};
|
||||
use super::CustomEvent;
|
||||
use crate::ipc::{new_listener, Connection, Data};
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
use hbb_common::tokio::sync::mpsc::unbounded_channel;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
use hbb_common::ResultType;
|
||||
use hbb_common::{
|
||||
allow_err, log,
|
||||
tokio::{
|
||||
self,
|
||||
sync::mpsc::{unbounded_channel, UnboundedReceiver},
|
||||
},
|
||||
tokio::{self, sync::mpsc::UnboundedReceiver},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::RwLock;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
use tao::event_loop::EventLoopProxy;
|
||||
#[cfg(target_os = "linux")]
|
||||
use winit::event_loop::EventLoopProxy;
|
||||
|
||||
lazy_static! {
|
||||
pub(super) static ref EVENT_PROXY: RwLock<Option<EventLoopProxy<(String, CustomEvent)>>> =
|
||||
RwLock::new(None);
|
||||
}
|
||||
|
||||
const RIPPLE_DURATION: Duration = Duration::from_millis(500);
|
||||
#[cfg(target_os = "macos")]
|
||||
type RippleFloat = f64;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
type RippleFloat = f32;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use super::linux::run;
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
pub fn run() {
|
||||
let (tx_exit, rx_exit) = unbounded_channel();
|
||||
std::thread::spawn(move || {
|
||||
start_ipc(rx_exit);
|
||||
});
|
||||
if let Err(e) = create_event_loop() {
|
||||
if let Err(e) = super::create_event_loop() {
|
||||
log::error!("Failed to create event loop: {}", e);
|
||||
tx_exit.send(()).ok();
|
||||
return;
|
||||
@@ -31,7 +45,7 @@ pub fn run() {
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn start_ipc(mut rx_exit: UnboundedReceiver<()>) {
|
||||
pub(super) async fn start_ipc(mut rx_exit: UnboundedReceiver<()>) {
|
||||
match new_listener("_whiteboard").await {
|
||||
Ok(mut incoming) => loop {
|
||||
tokio::select! {
|
||||
@@ -82,9 +96,7 @@ async fn handle_new_stream(mut conn: Connection) {
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
@@ -120,3 +132,40 @@ pub(super) fn get_displays_rect() -> ResultType<(i32, i32, u32, u32)> {
|
||||
let (w, h) = ((max_x - min_x) as u32, (max_y - min_y) as u32);
|
||||
Ok((x, y, w, h))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn argb_to_rgba(argb: u32) -> (u8, u8, u8, u8) {
|
||||
(
|
||||
(argb >> 16 & 0xFF) as u8,
|
||||
(argb >> 8 & 0xFF) as u8,
|
||||
(argb & 0xFF) as u8,
|
||||
(argb >> 24 & 0xFF) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) struct Ripple {
|
||||
pub x: RippleFloat,
|
||||
pub y: RippleFloat,
|
||||
pub start_time: Instant,
|
||||
}
|
||||
|
||||
impl Ripple {
|
||||
#[inline]
|
||||
pub fn retain_active(ripples: &mut Vec<Ripple>) {
|
||||
ripples.retain(|r| r.start_time.elapsed() < RIPPLE_DURATION);
|
||||
}
|
||||
|
||||
pub fn get_radius_alpha(&self) -> (RippleFloat, RippleFloat) {
|
||||
let elapsed = self.start_time.elapsed();
|
||||
#[cfg(target_os = "macos")]
|
||||
let progress = (elapsed.as_secs_f64() / RIPPLE_DURATION.as_secs_f64()).min(1.0);
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
let progress = (elapsed.as_secs_f32() / RIPPLE_DURATION.as_secs_f32()).min(1.0);
|
||||
#[cfg(target_os = "macos")]
|
||||
let radius = 25.0 * progress;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
let radius = 45.0 * progress;
|
||||
let alpha = 1.0 - progress;
|
||||
(radius, alpha)
|
||||
}
|
||||
}
|
||||
|
||||
180
src/whiteboard/win_linux.rs
Normal file
180
src/whiteboard/win_linux.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use hbb_common::{bail, ResultType};
|
||||
use tiny_skia::{FillRule, Paint, PathBuilder, PixmapMut, Point, Rect, Transform};
|
||||
use ttf_parser::Face;
|
||||
// A helper struct to bridge `ttf-parser` and `tiny-skia`.
|
||||
struct PathBuilderWrapper<'a> {
|
||||
path_builder: &'a mut PathBuilder,
|
||||
transform: Transform,
|
||||
}
|
||||
|
||||
impl ttf_parser::OutlineBuilder for PathBuilderWrapper<'_> {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
let mut pt = Point::from_xy(x, y);
|
||||
self.transform.map_point(&mut pt);
|
||||
self.path_builder.move_to(pt.x, pt.y);
|
||||
}
|
||||
|
||||
fn line_to(&mut self, x: f32, y: f32) {
|
||||
let mut pt = Point::from_xy(x, y);
|
||||
self.transform.map_point(&mut pt);
|
||||
self.path_builder.line_to(pt.x, pt.y);
|
||||
}
|
||||
|
||||
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
|
||||
let mut pt1 = Point::from_xy(x1, y1);
|
||||
self.transform.map_point(&mut pt1);
|
||||
let mut pt = Point::from_xy(x, y);
|
||||
self.transform.map_point(&mut pt);
|
||||
self.path_builder.quad_to(pt1.x, pt1.y, pt.x, pt.y);
|
||||
}
|
||||
|
||||
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
|
||||
let mut pt1 = Point::from_xy(x1, y1);
|
||||
self.transform.map_point(&mut pt1);
|
||||
let mut pt2 = Point::from_xy(x2, y2);
|
||||
self.transform.map_point(&mut pt2);
|
||||
let mut pt = Point::from_xy(x, y);
|
||||
self.transform.map_point(&mut pt);
|
||||
self.path_builder
|
||||
.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt.x, pt.y);
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.path_builder.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a string of text with the white background rectangle onto the pixmap.
|
||||
pub(super) fn draw_text(
|
||||
pixmap: &mut PixmapMut,
|
||||
face: &Face,
|
||||
text: &str,
|
||||
x: f32,
|
||||
y: f32,
|
||||
paint: &Paint,
|
||||
font_size: f32,
|
||||
) {
|
||||
let units_per_em = face.units_per_em() as f32;
|
||||
let scale = font_size / units_per_em;
|
||||
|
||||
// --- 1. Calculate text dimensions for the background ---
|
||||
let mut total_width = 0.0;
|
||||
for ch in text.chars() {
|
||||
let glyph_id = face.glyph_index(ch).unwrap_or_default();
|
||||
if let Some(h_advance) = face.glyph_hor_advance(glyph_id) {
|
||||
total_width += h_advance as f32 * scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Use font metrics for a consistent background height.
|
||||
let font_height = (face.ascender() - face.descender()) as f32 * scale;
|
||||
let ascent = face.ascender() as f32 * scale;
|
||||
// Add some padding around the text
|
||||
let padding = 3.0;
|
||||
|
||||
let mut bg_filled = false;
|
||||
// --- 2. Draw the white background rectangle ---
|
||||
if let Some(bg_rect) = Rect::from_xywh(
|
||||
x - padding,
|
||||
y - ascent - padding,
|
||||
total_width + 2.0 * padding,
|
||||
font_height + 2.0 * padding,
|
||||
) {
|
||||
// Corner radius
|
||||
let radius = 5.0;
|
||||
let path = {
|
||||
let mut pb = PathBuilder::new();
|
||||
let r_x = bg_rect.x();
|
||||
let r_y = bg_rect.y();
|
||||
let r_w = bg_rect.width();
|
||||
let r_h = bg_rect.height();
|
||||
pb.move_to(r_x + radius, r_y);
|
||||
pb.line_to(r_x + r_w - radius, r_y);
|
||||
pb.quad_to(r_x + r_w, r_y, r_x + r_w, r_y + radius);
|
||||
pb.line_to(r_x + r_w, r_y + r_h - radius);
|
||||
pb.quad_to(r_x + r_w, r_y + r_h, r_x + r_w - radius, r_y + r_h);
|
||||
pb.line_to(r_x + radius, r_y + r_h);
|
||||
pb.quad_to(r_x, r_y + r_h, r_x, r_y + r_h - radius);
|
||||
pb.line_to(r_x, r_y + radius);
|
||||
pb.quad_to(r_x, r_y, r_x + radius, r_y);
|
||||
pb.close();
|
||||
pb.finish()
|
||||
};
|
||||
|
||||
if let Some(path) = path {
|
||||
let mut bg_paint = Paint::default();
|
||||
bg_paint.set_color_rgba8(255, 255, 255, 255);
|
||||
bg_paint.anti_alias = true;
|
||||
pixmap.fill_path(
|
||||
&path,
|
||||
&bg_paint,
|
||||
FillRule::Winding,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
bg_filled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 3. Draw the text ---
|
||||
let transform = Transform::from_translate(x, y).pre_scale(scale, -scale);
|
||||
let mut path_builder = PathBuilder::new();
|
||||
let mut current_x = 0.0;
|
||||
|
||||
for ch in text.chars() {
|
||||
let glyph_id = face.glyph_index(ch).unwrap_or_default();
|
||||
|
||||
let mut builder = PathBuilderWrapper {
|
||||
path_builder: &mut path_builder,
|
||||
transform: transform.post_translate(current_x, 0.0),
|
||||
};
|
||||
|
||||
face.outline_glyph(glyph_id, &mut builder);
|
||||
|
||||
if let Some(h_advance) = face.glyph_hor_advance(glyph_id) {
|
||||
current_x += h_advance as f32 * scale;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = path_builder.finish() {
|
||||
if bg_filled {
|
||||
let mut text_paint = Paint::default();
|
||||
text_paint.set_color_rgba8(0, 0, 0, 255);
|
||||
text_paint.anti_alias = true;
|
||||
pixmap.fill_path(
|
||||
&path,
|
||||
&text_paint,
|
||||
FillRule::Winding,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
pixmap.fill_path(&path, paint, FillRule::Winding, Transform::identity(), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn create_font_face() -> ResultType<Face<'static>> {
|
||||
let mut font_db = fontdb::Database::new();
|
||||
font_db.load_system_fonts();
|
||||
let query = fontdb::Query {
|
||||
families: &[fontdb::Family::Monospace],
|
||||
..fontdb::Query::default()
|
||||
};
|
||||
let Some(font_id) = font_db.query(&query) else {
|
||||
bail!("No monospace font found!");
|
||||
};
|
||||
let Some((font_source, face_index)) = font_db.face_source(font_id) else {
|
||||
bail!("No face found for font!");
|
||||
};
|
||||
// Load the font data into a static slice to satisfy `ttf-parser`'s lifetime requirements.
|
||||
// We use `Box::leak` to leak the memory, which is acceptable here since the font data
|
||||
// is needed for the entire lifetime of the application.
|
||||
let font_data: &'static [u8] = Box::leak(match font_source {
|
||||
fontdb::Source::File(path) => std::fs::read(path)?.into_boxed_slice(),
|
||||
fontdb::Source::Binary(data) => data.as_ref().as_ref().to_vec().into_boxed_slice(),
|
||||
fontdb::Source::SharedFile(path, _) => std::fs::read(path)?.into_boxed_slice(),
|
||||
});
|
||||
let face = Face::parse(font_data, face_index)?;
|
||||
Ok(face)
|
||||
}
|
||||
@@ -1,121 +1,19 @@
|
||||
use super::{server::EVENT_PROXY, Cursor, CustomEvent};
|
||||
use hbb_common::{anyhow::anyhow, bail, log, ResultType};
|
||||
use super::{
|
||||
server::{Ripple, EVENT_PROXY},
|
||||
win_linux::{create_font_face, draw_text},
|
||||
Cursor, CustomEvent,
|
||||
};
|
||||
use hbb_common::{anyhow::anyhow, log, ResultType};
|
||||
use softbuffer::{Context, Surface};
|
||||
use std::{collections::HashMap, num::NonZeroU32, sync::Arc, time::Instant};
|
||||
#[cfg(target_os = "linux")]
|
||||
use tao::platform::unix::WindowBuilderExtUnix;
|
||||
#[cfg(target_os = "windows")]
|
||||
use tao::platform::windows::WindowBuilderExtWindows;
|
||||
use tao::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
platform::windows::WindowBuilderExtWindows,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use tiny_skia::{Color, FillRule, Paint, PathBuilder, PixmapMut, Point, Stroke, Transform};
|
||||
use ttf_parser::Face;
|
||||
|
||||
// A helper struct to bridge `ttf-parser` and `tiny-skia`.
|
||||
struct PathBuilderWrapper<'a> {
|
||||
path_builder: &'a mut PathBuilder,
|
||||
transform: Transform,
|
||||
}
|
||||
|
||||
impl ttf_parser::OutlineBuilder for PathBuilderWrapper<'_> {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
let mut pt = Point::from_xy(x, y);
|
||||
self.transform.map_point(&mut pt);
|
||||
self.path_builder.move_to(pt.x, pt.y);
|
||||
}
|
||||
|
||||
fn line_to(&mut self, x: f32, y: f32) {
|
||||
let mut pt = Point::from_xy(x, y);
|
||||
self.transform.map_point(&mut pt);
|
||||
self.path_builder.line_to(pt.x, pt.y);
|
||||
}
|
||||
|
||||
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
|
||||
let mut pt1 = Point::from_xy(x1, y1);
|
||||
self.transform.map_point(&mut pt1);
|
||||
let mut pt = Point::from_xy(x, y);
|
||||
self.transform.map_point(&mut pt);
|
||||
self.path_builder.quad_to(pt1.x, pt1.y, pt.x, pt.y);
|
||||
}
|
||||
|
||||
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
|
||||
let mut pt1 = Point::from_xy(x1, y1);
|
||||
self.transform.map_point(&mut pt1);
|
||||
let mut pt2 = Point::from_xy(x2, y2);
|
||||
self.transform.map_point(&mut pt2);
|
||||
let mut pt = Point::from_xy(x, y);
|
||||
self.transform.map_point(&mut pt);
|
||||
self.path_builder
|
||||
.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt.x, pt.y);
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.path_builder.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a string of text onto the pixmap.
|
||||
fn draw_text(
|
||||
pixmap: &mut PixmapMut,
|
||||
face: &Face,
|
||||
text: &str,
|
||||
x: f32,
|
||||
y: f32,
|
||||
paint: &Paint,
|
||||
font_size: f32,
|
||||
) {
|
||||
let units_per_em = face.units_per_em() as f32;
|
||||
let scale = font_size / units_per_em;
|
||||
let transform = Transform::from_translate(x, y).pre_scale(scale, -scale);
|
||||
|
||||
let mut path_builder = PathBuilder::new();
|
||||
let mut current_x = 0.0;
|
||||
|
||||
for ch in text.chars() {
|
||||
let glyph_id = face.glyph_index(ch).unwrap_or_default();
|
||||
|
||||
let mut builder = PathBuilderWrapper {
|
||||
path_builder: &mut path_builder,
|
||||
transform: transform.post_translate(current_x, 0.0),
|
||||
};
|
||||
|
||||
face.outline_glyph(glyph_id, &mut builder);
|
||||
|
||||
if let Some(h_advance) = face.glyph_hor_advance(glyph_id) {
|
||||
current_x += h_advance as f32 * scale;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = path_builder.finish() {
|
||||
pixmap.fill_path(&path, paint, FillRule::Winding, Transform::identity(), None);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_font_face() -> ResultType<Face<'static>> {
|
||||
let mut font_db = fontdb::Database::new();
|
||||
font_db.load_system_fonts();
|
||||
let query = fontdb::Query {
|
||||
families: &[fontdb::Family::Monospace],
|
||||
..fontdb::Query::default()
|
||||
};
|
||||
let Some(font_id) = font_db.query(&query) else {
|
||||
bail!("No monospace font found!");
|
||||
};
|
||||
let Some((font_source, face_index)) = font_db.face_source(font_id) else {
|
||||
bail!("No face found for font!");
|
||||
};
|
||||
let font_data: &'static [u8] = Box::leak(match font_source {
|
||||
fontdb::Source::File(path) => std::fs::read(path)?.into_boxed_slice(),
|
||||
fontdb::Source::Binary(data) => data.as_ref().as_ref().to_vec().into_boxed_slice(),
|
||||
fontdb::Source::SharedFile(path, _) => std::fs::read(path)?.into_boxed_slice(),
|
||||
});
|
||||
let face = Face::parse(font_data, face_index)?;
|
||||
Ok(face)
|
||||
}
|
||||
use tiny_skia::{Color, FillRule, Paint, PathBuilder, PixmapMut, Stroke, Transform};
|
||||
|
||||
pub(super) fn create_event_loop() -> ResultType<()> {
|
||||
let face = match create_font_face() {
|
||||
@@ -171,11 +69,6 @@ pub(super) fn create_event_loop() -> ResultType<()> {
|
||||
}),
|
||||
};
|
||||
|
||||
struct Ripple {
|
||||
x: f32,
|
||||
y: f32,
|
||||
start_time: Instant,
|
||||
}
|
||||
let mut ripples: Vec<Ripple> = Vec::new();
|
||||
let mut last_cursors: HashMap<String, Cursor> = HashMap::new();
|
||||
let mut resized = final_size.is_none();
|
||||
@@ -230,23 +123,17 @@ pub(super) fn create_event_loop() -> ResultType<()> {
|
||||
};
|
||||
pixmap.fill(Color::TRANSPARENT);
|
||||
|
||||
let ripple_duration = std::time::Duration::from_millis(500);
|
||||
ripples.retain(|r| r.start_time.elapsed() < ripple_duration);
|
||||
|
||||
Ripple::retain_active(&mut ripples);
|
||||
for ripple in &ripples {
|
||||
let elapsed = ripple.start_time.elapsed();
|
||||
let progress = elapsed.as_secs_f32() / ripple_duration.as_secs_f32();
|
||||
let radius = 45.0 * progress;
|
||||
let alpha = 1.0 - progress;
|
||||
let (radius, alpha) = ripple.get_radius_alpha();
|
||||
|
||||
let mut ripple_paint = Paint::default();
|
||||
// Note: The real color is bgra here.
|
||||
ripple_paint.set_color_rgba8(128, 128, 255, (alpha * 128.0) as u8);
|
||||
ripple_paint.set_color_rgba8(64, 64, 255, (alpha * 128.0) as u8);
|
||||
ripple_paint.anti_alias = true;
|
||||
|
||||
let mut ripple_pb = PathBuilder::new();
|
||||
let (rx, ry) = (ripple.x as f64, ripple.y as f64);
|
||||
ripple_pb.push_circle(rx as f32, ry as f32, radius as f32);
|
||||
ripple_pb.push_circle(ripple.x, ripple.y, radius);
|
||||
if let Some(path) = ripple_pb.finish() {
|
||||
pixmap.fill_path(
|
||||
&path,
|
||||
@@ -259,9 +146,8 @@ pub(super) fn create_event_loop() -> ResultType<()> {
|
||||
}
|
||||
|
||||
for cursor in last_cursors.values() {
|
||||
let (x, y) = (cursor.x as f64, cursor.y as f64);
|
||||
let (x, y) = (x as f32, y as f32);
|
||||
let size = 1.5 as f32;
|
||||
let (x, y) = (cursor.x, cursor.y);
|
||||
let size = 1.5f32;
|
||||
|
||||
let mut pb = PathBuilder::new();
|
||||
pb.move_to(x, y);
|
||||
@@ -274,14 +160,10 @@ pub(super) fn create_event_loop() -> ResultType<()> {
|
||||
pb.close();
|
||||
|
||||
if let Some(path) = pb.finish() {
|
||||
let rgba = super::argb_to_rgba(cursor.argb);
|
||||
let mut arrow_paint = Paint::default();
|
||||
// Note: The real color is bgra here.
|
||||
arrow_paint.set_color_rgba8(
|
||||
(cursor.argb & 0xFF) as u8,
|
||||
(cursor.argb >> 8 & 0xFF) as u8,
|
||||
(cursor.argb >> 16 & 0xFF) as u8,
|
||||
(cursor.argb >> 24 & 0xFF) as u8,
|
||||
);
|
||||
arrow_paint.set_color_rgba8(rgba.2, rgba.1, rgba.0, rgba.3);
|
||||
arrow_paint.anti_alias = true;
|
||||
pixmap.fill_path(
|
||||
&path,
|
||||
@@ -295,7 +177,7 @@ pub(super) fn create_event_loop() -> ResultType<()> {
|
||||
black_paint.set_color_rgba8(0, 0, 0, 255);
|
||||
black_paint.anti_alias = true;
|
||||
let mut stroke = Stroke::default();
|
||||
stroke.width = 1.0 as f32;
|
||||
stroke.width = 1.0f32;
|
||||
pixmap.stroke_path(
|
||||
&path,
|
||||
&black_paint,
|
||||
@@ -312,7 +194,7 @@ pub(super) fn create_event_loop() -> ResultType<()> {
|
||||
x + 24.0 * size,
|
||||
y + 24.0 * size,
|
||||
&arrow_paint,
|
||||
24.0 as f32,
|
||||
14.0f32,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user