From 539c5e2a4cfc84ca761b0178a68a37d4be866eb5 Mon Sep 17 00:00:00 2001 From: chayleaf Date: Sun, 25 Aug 2024 04:05:07 +0700 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 646 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 10 + shell.nix | 8 + src/graphics.rs | 364 +++++++++++++++++++++++++++ src/keyboard.rs | 119 +++++++++ src/main.rs | 25 ++ src/wayland.rs | 350 ++++++++++++++++++++++++++ 8 files changed, 1523 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 shell.nix create mode 100644 src/graphics.rs create mode 100644 src/keyboard.rs create mode 100644 src/main.rs create mode 100644 src/wayland.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c637e63 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,646 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bytemuck" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags", + "log", + "polling", + "rustix", + "slab", + "thiserror", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "font-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0189ccb084f77c5523e08288d418cbaa09c451a08515678a0aa265df9a8b60" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "hboard" +version = "0.1.0" +dependencies = [ + "image", + "log", + "smithay-client-toolkit", + "swash", +] + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "image" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "read-fonts" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c141b9980e1150201b2a3a32879001c8f975fe313ec3df5471a9b5c79a880cd" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "skrifa" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abea4738067b1e628c6ce28b2c216c19e9ea95715cdb332680e821c3bec2ef23" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags", + "bytemuck", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.4", + "pkg-config", + "rustix", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkbcommon", + "xkeysym", +] + +[[package]] +name = "swash" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93cdc334a50fcc2aa3f04761af3b28196280a6aaadb1ef11215c478ae32615ac" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wayland-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" +dependencies = [ + "bitflags", + "rustix", + "wayland-backend", + "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", + "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", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xkbcommon" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" +dependencies = [ + "libc", + "memmap2 0.8.0", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0584fc7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hboard" +version = "0.1.0" +edition = "2021" + +[dependencies] +image = { version = "0.25.2", default-features = false } +log = "0.4.22" +smithay-client-toolkit = "0.19.2" +swash = "0.1.18" diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..0718a78 --- /dev/null +++ b/shell.nix @@ -0,0 +1,8 @@ +{ pkgs ? import {} }: + +pkgs.mkShell rec { + name = "shell-rust"; + nativeBuildInputs = with pkgs; [ rustc cargo pkg-config ]; + buildInputs = with pkgs; [ libxkbcommon ]; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; +} diff --git a/src/graphics.rs b/src/graphics.rs new file mode 100644 index 0000000..8e2407b --- /dev/null +++ b/src/graphics.rs @@ -0,0 +1,364 @@ +use std::{ + borrow::Cow, + collections::{BTreeMap, HashMap}, +}; + +use image::Pixel; +use swash::{ + scale::{ + image::{Content, Image}, + Render, ScaleContext, Scaler, Source, StrikeWith, + }, + shape::{Direction, ShapeContext, Shaper}, + text::Script, + FontRef, +}; + +pub struct Canvas<'a> { + pub(crate) width: u32, + pub(crate) height: u32, + pub(crate) data: &'a mut [u8], + pub(crate) glyph_cache: HashMap<(FontId, u16), Option>, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct FontId(u16); + +impl<'a> Canvas<'a> { + pub fn as_image(&mut self) -> Option, &mut [u8]>> { + image::ImageBuffer::from_raw(self.width, self.height, self.data) + } + fn render<'d, 'b: 'd, 'c>( + glyph_cache: &'b mut HashMap<(FontId, u16), Option>, + render: &'c mut Render, + scaler: &'c mut Scaler, + font: FontId, + id: u16, + ) -> Option<&'d Image> { + glyph_cache + .entry((font, id)) + .or_insert_with(|| { + let mut img2 = Image::new(); + if let Some(val) = render.render_into(scaler, id, &mut img2).then_some(img2) { + Some(val) + } else { + None + } + }) + .as_ref() + } + pub fn text_size(&mut self, shaper: Shaper, scaler: &mut Scaler, font: FontId) -> (f32, f32) { + let mut ret_x = 0.0; + let mut ret_y = 0.0f32; + let mut render = Render::new(&[ + Source::ColorOutline(0), + Source::ColorBitmap(StrikeWith::BestFit), + Source::Bitmap(StrikeWith::BestFit), + Source::Outline, + ]); + shaper.shape_with(|cluster| { + for glyph in cluster.glyphs { + ret_x += glyph.advance; + render.offset((ret_x % 1.0, -(0.0 % 1.0)).into()); + let Some(img2) = + Self::render(&mut self.glyph_cache, &mut render, scaler, font, glyph.id) + else { + continue; + }; + ret_y = ret_y.max(img2.placement.top as f32); + } + }); + (ret_x, ret_y) + } + pub fn render_text( + &mut self, + shaper: Shaper, + scaler: &mut Scaler, + font: FontId, + x_pos: f32, + mut y_pos: f32, + bounds: Rect, + dir: Direction, + ) { + y_pos = self.height as f32 - y_pos; + let max_xpos = bounds.left + bounds.width; + let max_ypos = bounds.bottom + bounds.height; + let min_xpos = bounds.left; + let min_ypos = bounds.bottom; + let mut render = Render::new(&[ + Source::ColorOutline(0), + Source::ColorBitmap(StrikeWith::BestFit), + Source::Bitmap(StrikeWith::BestFit), + Source::Outline, + ]); + let mut cur_x = match dir { + Direction::LeftToRight => x_pos, + Direction::RightToLeft => bounds.width as f32 - x_pos, + }; + let cur_y = y_pos; + shaper.shape_with(|cluster| { + for glyph in cluster.glyphs { + match dir { + Direction::LeftToRight => {} + Direction::RightToLeft => cur_x -= glyph.advance, + } + let off_x = cur_x; // - glyph.x; + let off_y = cur_y - glyph.y; + match dir { + Direction::LeftToRight => cur_x += glyph.advance, + Direction::RightToLeft => {} + } + render.offset((off_x % 1.0, -(off_y % 1.0)).into()); + let id = glyph.id; + let mut off_x = off_x as i64; + let mut off_y = off_y as i64; + let Some(img2) = Self::render(&mut self.glyph_cache, &mut render, scaler, font, id) + else { + continue; + }; + let (stride, _is_mask) = match img2.content { + Content::Mask => (1, true), + Content::SubpixelMask => (4, true), + Content::Color => (4, false), + }; + let plc = img2.placement; + let w = plc.width; + if w == 0 { + continue; + } + // origin is bottom left, + // so off_x is added, off_y is subtracted + off_y -= i64::from(plc.top); + off_x += i64::from(plc.left); + let data = &img2.data; + let mut img = image::ImageBuffer::, &mut [u8]>::from_raw( + self.width, + self.height, + self.data, + ) + .unwrap(); + let max_h = max_ypos.saturating_sub( + img.height() + .saturating_sub(u32::try_from(off_y + plc.height as i64).unwrap_or(0)), + ); + let min_h = min_ypos.saturating_sub( + img.height() + .saturating_sub(u32::try_from(off_y + plc.height as i64).unwrap_or(0)), + ); + let actual_h = plc.height; + let max_w = usize::try_from(max_xpos) + .unwrap() + .saturating_sub(usize::try_from(off_x).unwrap_or(0)); + let min_w = usize::try_from(min_xpos) + .unwrap() + .saturating_sub(usize::try_from(off_x).unwrap_or(0)); + for (row_in, (_, row_out)) in data + .chunks_exact(usize::try_from(w * stride).unwrap()) + .skip( + usize::try_from(-off_y).unwrap_or(0) + + usize::try_from(actual_h.saturating_sub(max_h)).unwrap(), + ) + .zip( + img.enumerate_rows_mut() + .skip( + usize::try_from(off_y).unwrap_or(0) + + usize::try_from(actual_h.saturating_sub(max_h)).unwrap(), + ) + .take( + actual_h + .saturating_sub( + u32::try_from(-off_y).unwrap_or(0) + + actual_h.saturating_sub(max_h), + ) + .saturating_sub(min_h) as usize, + ), + ) + { + for (pix_in, pix_out) in row_in + .chunks_exact(usize::try_from(stride).unwrap()) + .skip(usize::try_from(-off_x).unwrap_or(0) + min_w) + .take(max_w.saturating_sub(min_w)) + .zip( + row_out + .skip(usize::try_from(off_x).unwrap_or(0) + min_w) + .take(max_w.saturating_sub(min_w)), + ) + { + if stride == 4 { + pix_out + .2 + .blend(&image::Rgba(<[u8; 4]>::try_from(pix_in).unwrap())); + } else { + pix_out.2.blend(&image::Rgba([255, 255, 255, pix_in[0]])); + } + } + } + } + }); + } +} + +#[derive(Copy, Clone, Default)] +pub struct Size { + num: u32, + parent_size_sub: u32, + percent_from_parent_size: f32, +} + +impl Size { + pub fn zero() -> Self { + Self::default() + } + pub fn full() -> Self { + Self::ratio(1.0) + } + pub fn ratio(x: f32) -> Self { + Self { + num: 0, + parent_size_sub: 0, + percent_from_parent_size: x, + } + } +} + +pub trait Widget<'a> { + /// render with x/y as the bottom left corner + fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect); +} + +#[derive(Default)] +pub struct Layout<'a> { + #[allow(clippy::type_complexity)] + children: BTreeMap<(i32, String), (Rect, Rect, Box>)>, + z_levels: BTreeMap, +} + +#[derive(Copy, Clone, Default)] +pub struct Rect { + pub left: T, + pub bottom: T, + pub width: T, + pub height: T, +} + +fn resolve_size(size: Size, parent_size: u32) -> u32 { + ((parent_size.saturating_sub(size.parent_size_sub) as f64) + * size.percent_from_parent_size as f64) as u32 + + size.num +} + +fn resolve_rect_size(size: Rect, w: u32, h: u32) -> Rect { + Rect { + left: resolve_size(size.left, w), + bottom: resolve_size(size.bottom, h), + width: resolve_size(size.width, w), + height: resolve_size(size.height, h), + } +} + +impl<'a> Layout<'a> { + pub fn add_child( + &mut self, + widget: Box>, + z: i32, + id: &str, + bounds: Rect, + ) { + if let Some(old_z) = self.z_levels.insert(id.to_owned(), z) { + self.children.remove(&(old_z, id.to_owned())); + } + self.children + .insert((z, id.to_owned()), (bounds, Rect::default(), widget)); + } +} + +impl<'a> Widget<'a> for Layout<'a> { + fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect) { + for (size, size_resolved, widget) in self.children.values_mut() { + *size_resolved = resolve_rect_size(*size, bounds.width, bounds.height); + size_resolved.left += bounds.left; + size_resolved.bottom += bounds.bottom; + size_resolved.width = size_resolved.width.min(bounds.width - size_resolved.left); + size_resolved.height = size_resolved + .height + .min(bounds.height - size_resolved.bottom); + widget.render(canvas, *size_resolved); + } + } +} + +#[derive(Default)] +pub enum Alignment { + #[default] + Start, + Middle, + End, +} + +pub struct Text<'a> { + pub text: Cow<'a, str>, + pub font: FontRef<'a>, + pub font_id: u16, + pub font_size: f32, + pub script: Script, + pub direction: Direction, + pub halign: Alignment, + pub valign: Alignment, +} + +impl<'a> Widget<'a> for Text<'a> { + fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect) { + let mut context = ShapeContext::new(); + let mut shaper = context + .builder(self.font) + .script(self.script) + .direction(self.direction) + .size(self.font_size) + .build(); + shaper.add_str(&self.text); + let mut context2 = ScaleContext::new(); + let mut scaler = context2 + .builder(self.font) + .size(self.font_size) + .hint(true) + .build(); + let (text_w, mut _text_h) = canvas.text_size(shaper, &mut scaler, FontId(self.font_id)); + let text_h = self.font_size / 1.2; + shaper = context + .builder(self.font) + .script(self.script) + .direction(self.direction) + .size(self.font_size) + .build(); + shaper.add_str(&self.text); + let mut x = bounds.left as i32; + let mut y = bounds.bottom as i32; + match self.halign { + Alignment::End => { + x += bounds.width as i32 - text_w as i32; + } + Alignment::Middle => { + x += ((bounds.width as f32 - text_w) / 2.0) as i32; + } + Alignment::Start => {} + } + match self.valign { + Alignment::End => { + y += bounds.height as i32 - text_h as i32; + } + Alignment::Middle => { + y += ((bounds.height as f32 - text_h) / 2.0) as i32; + } + Alignment::Start => {} + } + canvas.render_text( + shaper, + &mut scaler, + FontId(self.font_id), + x as f32, + y as f32, + bounds, + self.direction, + ); + } +} diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 0000000..4ebcf43 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,119 @@ +use swash::{shape::Direction, text::Script, FontRef}; + +use crate::graphics::{self, Canvas, Widget}; + +pub struct Keyboard<'a> { + latin_font: FontRef<'a>, + emoji_font: FontRef<'a>, + arabic_font: FontRef<'a>, + width: u32, + height: u32, + shift: u32, +} + +impl<'a> Keyboard<'a> { + pub fn new(latin_font: FontRef<'a>, emoji_font: FontRef<'a>, arabic_font: FontRef<'a>) -> Self { + Self { + latin_font, + emoji_font, + arabic_font, + width: 720, + height: 480, + shift: 0, + } + } + pub fn size(&self) -> (u32, u32) { + (self.width, self.height) + } + pub fn set_size(&mut self, width: u32, height: u32) { + self.width = width; + self.height = height; + } + fn render_text(&mut self, canvas: &mut [u8]) { + let width = self.width; + let height = self.height; + let mut layout = graphics::Layout::default(); + layout.add_child( + Box::new(graphics::Text { + text: "عَرَبِيّ".into(), + font: self.arabic_font, + font_id: 2, + halign: graphics::Alignment::Middle, + valign: graphics::Alignment::Middle, + direction: Direction::RightToLeft, + font_size: 128.0, + script: Script::Arabic, + }), + 0, + "a", + graphics::Rect { + left: graphics::Size::zero(), + bottom: graphics::Size::ratio(0.0), + width: graphics::Size::ratio(0.5), + height: graphics::Size::ratio(1.0), + }, + ); + layout.add_child( + Box::new(graphics::Text { + text: "🔥🗣️🔥🗣️🔥🗣️".into(), + font: self.emoji_font, + font_id: 1, + halign: graphics::Alignment::Middle, + valign: graphics::Alignment::Middle, + direction: Direction::LeftToRight, + font_size: 128.0, + script: Script::Latin, + }), + 0, + "b", + graphics::Rect { + left: graphics::Size::ratio(0.5), + bottom: graphics::Size::ratio(0.0), + width: graphics::Size::ratio(0.5), + height: graphics::Size::ratio(1.0), + }, + ); + let mut canvas = Canvas { + width: self.width, + height: self.height, + data: canvas, + glyph_cache: Default::default(), + }; + layout.render( + &mut canvas, + graphics::Rect { + left: 0, + bottom: 0, + width, + height, + }, + ); + } + // rgba + pub fn draw(&mut self, canvas: &mut [u8]) { + let width = self.width; + let height = self.height; + canvas + .chunks_exact_mut(4) + .enumerate() + .for_each(|(index, chunk)| { + let x = ((index + self.shift as usize) % width as usize) as u32; + let y = (index / width as usize) as u32; + + let a = 0xFF; + let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height); + let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height); + let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height); + + let array: &mut [u8; 4] = chunk.try_into().unwrap(); + *array = [ + r.try_into().unwrap_or(255), + g.try_into().unwrap_or(255), + b.try_into().unwrap_or(255), + a.try_into().unwrap_or(255), + ]; + }); + self.render_text(canvas); + self.shift = (self.shift + 1) % width; + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..279f7cb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,25 @@ +use std::{fs, io, path::Path}; + +use keyboard::Keyboard; +use swash::FontRef; +mod graphics; +mod keyboard; +mod wayland; + +fn load_font(path: impl AsRef) -> Result, io::Error> { + let font_data = fs::read(path)?; + let data = Box::leak(font_data.into_boxed_slice()); + FontRef::from_index(data, 0) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid font file")) +} + +fn main() { + let font = load_font("/home/user/.nix-profile/share/fonts/noto/NotoSans[wdth,wght].ttf") + .expect("failed to load font"); + let font2 = load_font("/home/user/.nix-profile/share/fonts/noto/NotoColorEmoji.ttf") + .expect("failed to load font"); + let font3 = load_font("/home/user/.nix-profile/share/fonts/noto/NotoSansArabic[wdth,wght].ttf") + .expect("failed to load font"); + let kbd = Keyboard::new(font, font2, font3); + wayland::run(kbd); +} diff --git a/src/wayland.rs b/src/wayland.rs new file mode 100644 index 0000000..6eb9328 --- /dev/null +++ b/src/wayland.rs @@ -0,0 +1,350 @@ +use smithay_client_toolkit::{ + compositor::{CompositorHandler, CompositorState}, + delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry, + delegate_seat, delegate_shm, + output::{OutputHandler, OutputState}, + reexports::client::{ + globals::registry_queue_init, + protocol::{wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, + Connection, QueueHandle, + }, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, + seat::{ + pointer::{PointerEvent, PointerEventKind, PointerHandler}, + Capability, SeatHandler, SeatState, + }, + shell::{ + wlr_layer::{ + Anchor, Layer, LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure, + }, + WaylandSurface, + }, + shm::{slot::SlotPool, Shm, ShmHandler}, +}; + +use crate::keyboard::Keyboard; + +pub fn run(keyboard: Keyboard<'static>) { + let conn = Connection::connect_to_env().unwrap(); + let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); + let qh = event_queue.handle(); + let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor is not available"); + let layer_shell = LayerShell::bind(&globals, &qh).expect("layer shell is not available"); + let shm = Shm::bind(&globals, &qh).expect("wl_shm is not available"); + let surface = compositor.create_surface(&qh); + let layer = + layer_shell.create_layer_surface(&qh, surface, Layer::Top, Some("simple_layer"), None); + layer.set_anchor(Anchor::BOTTOM); + let (w, h) = keyboard.size(); + layer.set_size(w, h); + layer.commit(); + let pool = SlotPool::new( + usize::try_from(w).unwrap() * usize::try_from(h).unwrap() * 4, + &shm, + ) + .expect("Failed to create pool"); + let mut simple_layer = KeyboardLayer { + // Seats and outputs may be hotplugged at runtime, therefore we need to setup a registry state to + // listen for seats and outputs. + registry_state: RegistryState::new(&globals), + seat_state: SeatState::new(&globals, &qh), + output_state: OutputState::new(&globals, &qh), + shm, + + exit: false, + first_configure: true, + pool, + width: 256, + height: 256, + shift: None, + layer, + pointer: None, + keyboard, + }; + loop { + event_queue.blocking_dispatch(&mut simple_layer).unwrap(); + + if simple_layer.exit { + println!("exiting example"); + break; + } + } +} + +struct KeyboardLayer { + registry_state: RegistryState, + seat_state: SeatState, + output_state: OutputState, + shm: Shm, + + exit: bool, + first_configure: bool, + pool: SlotPool, + width: u32, + height: u32, + shift: Option, + layer: LayerSurface, + pointer: Option, + keyboard: Keyboard<'static>, +} + +impl KeyboardLayer { + pub fn draw(&mut self, qh: &QueueHandle) { + let width = self.width; + let height = self.height; + let stride = self.width as i32 * 4; + + let (buffer, canvas) = self + .pool + .create_buffer( + width as i32, + height as i32, + stride, + wl_shm::Format::Argb8888, + ) + .expect("create buffer"); + + // Draw to the window: + { + self.keyboard.set_size(width, height); + self.keyboard.draw(canvas); + // rgba->argb + canvas.chunks_exact_mut(4).for_each(|chunk| { + chunk.reverse(); + chunk.rotate_left(1); + }); + } + + // Damage the entire window + self.layer + .wl_surface() + .damage_buffer(0, 0, width as i32, height as i32); + + // Request our next frame + self.layer + .wl_surface() + .frame(qh, self.layer.wl_surface().clone()); + + // Attach and commit to present. + buffer + .attach_to(self.layer.wl_surface()) + .expect("buffer attach"); + self.layer.commit(); + + // TODO save and reuse buffer when the window size is unchanged. This is especially + // useful if you do damage tracking, since you don't need to redraw the undamaged parts + // of the canvas. + } +} + +impl CompositorHandler for KeyboardLayer { + fn scale_factor_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _new_factor: i32, + ) { + } + + fn transform_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _new_transform: wl_output::Transform, + ) { + } + + fn frame( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _time: u32, + ) { + self.draw(qh); + } + + fn surface_enter( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _output: &wl_output::WlOutput, + ) { + } + + fn surface_leave( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _output: &wl_output::WlOutput, + ) { + } +} + +impl OutputHandler for KeyboardLayer { + fn output_state(&mut self) -> &mut OutputState { + &mut self.output_state + } + + fn new_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } + + fn update_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } + + fn output_destroyed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } +} + +impl LayerShellHandler for KeyboardLayer { + fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle, _layer: &LayerSurface) { + self.exit = true; + } + + fn configure( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + _layer: &LayerSurface, + configure: LayerSurfaceConfigure, + _serial: u32, + ) { + if configure.new_size.0 == 0 || configure.new_size.1 == 0 { + self.width = 256; + self.height = 256; + } else { + self.width = configure.new_size.0; + self.height = configure.new_size.1; + } + + // Initiate the first draw. + if self.first_configure { + self.first_configure = false; + self.draw(qh); + } + } +} + +impl SeatHandler for KeyboardLayer { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} + + fn new_capability( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + seat: wl_seat::WlSeat, + capability: Capability, + ) { + if capability == Capability::Pointer && self.pointer.is_none() { + println!("Set pointer capability"); + let pointer = self + .seat_state + .get_pointer(qh, &seat) + .expect("Failed to create pointer"); + self.pointer = Some(pointer); + } + } + + fn remove_capability( + &mut self, + _conn: &Connection, + _: &QueueHandle, + _: wl_seat::WlSeat, + capability: Capability, + ) { + if capability == Capability::Pointer && self.pointer.is_some() { + println!("Unset pointer capability"); + self.pointer.take().unwrap().release(); + } + } + + fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} +} + +impl PointerHandler for KeyboardLayer { + fn pointer_frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _pointer: &wl_pointer::WlPointer, + events: &[PointerEvent], + ) { + for event in events { + // Ignore events for other surfaces + if &event.surface != self.layer.wl_surface() { + continue; + } + match event.kind { + PointerEventKind::Enter { .. } => { + println!("Pointer entered @{:?}", event.position); + } + PointerEventKind::Leave { .. } => { + println!("Pointer left"); + } + PointerEventKind::Motion { .. } => {} + PointerEventKind::Press { button, .. } => { + println!("Press {:x} @ {:?}", button, event.position); + self.shift = self.shift.xor(Some(0)); + } + PointerEventKind::Release { button, .. } => { + println!("Release {:x} @ {:?}", button, event.position); + } + PointerEventKind::Axis { + horizontal, + vertical, + .. + } => { + println!("Scroll H:{horizontal:?}, V:{vertical:?}"); + } + } + } + } +} + +impl ShmHandler for KeyboardLayer { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm + } +} + +delegate_compositor!(KeyboardLayer); +delegate_output!(KeyboardLayer); +delegate_shm!(KeyboardLayer); + +delegate_seat!(KeyboardLayer); +delegate_pointer!(KeyboardLayer); + +delegate_layer!(KeyboardLayer); + +delegate_registry!(KeyboardLayer); + +impl ProvidesRegistryState for KeyboardLayer { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + registry_handlers![OutputState, SeatState]; +}