From 01e0871111e2e90755c5cbadf6cef340e81321cf Mon Sep 17 00:00:00 2001 From: chayleaf Date: Wed, 4 Sep 2024 05:28:08 +0700 Subject: [PATCH] basic scheme integration --- .cargo/config.toml | 16 + .gitignore | 3 + Cargo.lock | 567 +++++++++++++++---- Cargo.toml | 28 +- README.md | 25 + defs.scm | 30 ++ shell.nix | 1 + src/framebuffer.rs | 252 +++++++++ src/graphics.rs | 694 +++++++++++++++++------- src/image.rs | 298 ++++++++++ src/keyboard.rs | 436 +++++++++++---- src/main.rs | 543 ++++++++++++++++++- src/script.rs | 754 ++++++++++++++++++++++++++ src/serde.rs | 1195 +++++++++++++++++++++++++++++++++++++++++ src/text.rs | 185 +++++++ src/wayland.rs | 290 ++++++++-- src/wayland/ime_v1.rs | 168 ++++++ src/wayland/ime_v2.rs | 209 +++++++ tmp.scm | 91 ++++ 19 files changed, 5340 insertions(+), 445 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 README.md create mode 100644 defs.scm create mode 100644 src/framebuffer.rs create mode 100644 src/image.rs create mode 100644 src/script.rs create mode 100644 src/serde.rs create mode 100644 src/text.rs create mode 100644 src/wayland/ime_v1.rs create mode 100644 src/wayland/ime_v2.rs create mode 100644 tmp.scm diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..7266e0b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,16 @@ +[profile.release-with-debug] +inherits = "release" +debug = true + +[profile.min-size] +inherits = "release" +# gives 1mb +strip = true +# gives 0.3mb +lto = true +# gives 0.2mb +panic = "abort" +# gives 0.5mb +opt-level = "z" +# gives 0.1mb +codegen-units = 1 diff --git a/.gitignore b/.gitignore index ea8c4bf..51fdb12 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ /target +perf.data* +flamegraph.svg +report.json diff --git a/Cargo.lock b/Cargo.lock index c637e63..db35c84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,93 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -34,38 +115,6 @@ dependencies = [ "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" @@ -75,6 +124,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -82,19 +140,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ - "crossbeam-utils", + "glob", + "libc", + "libloading", ] [[package]] -name = "crossbeam-utils" -version = "0.8.20" +name = "colorchoice" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "ctrlc" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] [[package]] name = "cursor-icon" @@ -117,6 +193,39 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -136,33 +245,72 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hboard" version = "0.1.0" dependencies = [ - "image", + "ctrlc", + "env_logger", + "libc", + "linuxfb", "log", + "marwood", + "serde", "smithay-client-toolkit", "swash", + "toml_edit", + "wayland-protocols-misc", ] [[package]] -name = "hermit-abi" -version = "0.4.0" +name = "home" +version = "0.5.9" 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" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "bytemuck", - "byteorder-lite", - "num-traits", + "windows-sys 0.52.0", ] +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.158" @@ -185,12 +333,33 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linuxfb" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51f313066dd86b36a3bf20ba93cfa57b5513da811745b296481923606d0bec" +dependencies = [ + "bindgen", + "libc", + "memmap", +] + [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "marwood" +version = "0.5.0" +dependencies = [ + "lazy_static", + "log", + "num", + "thiserror", +] + [[package]] name = "memchr" version = "2.7.4" @@ -198,12 +367,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "memmap2" -version = "0.8.0" +name = "memmap" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" dependencies = [ "libc", + "winapi", ] [[package]] @@ -215,6 +385,98 @@ dependencies = [ "libc", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -225,10 +487,16 @@ dependencies = [ ] [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pkg-config" @@ -237,18 +505,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] -name = "polling" -version = "3.7.3" +name = "prettyplease" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.59.0", + "proc-macro2", + "syn", ] [[package]] @@ -288,6 +551,41 @@ dependencies = [ "font-types", ] +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.38.34" @@ -307,6 +605,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "shlex" version = "1.3.0" @@ -323,15 +641,6 @@ dependencies = [ "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" @@ -345,14 +654,10 @@ 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", + "memmap2", "rustix", "thiserror", "wayland-backend", @@ -362,7 +667,6 @@ dependencies = [ "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", - "xkbcommon", "xkeysym", ] @@ -409,20 +713,21 @@ dependencies = [ ] [[package]] -name = "tracing" -version = "0.1.40" +name = "toml_datetime" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-core", -] +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] -name = "tracing-core" -version = "0.1.32" +name = "toml_edit" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] [[package]] name = "unicode-ident" @@ -430,6 +735,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wayland-backend" version = "0.3.6" @@ -490,6 +801,19 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols-misc" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfe44d48a0e51909c89da297f6b43bb814b881c78b69e254a4558a5fa8752887" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-wlr" version = "0.3.3" @@ -525,6 +849,40 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" @@ -607,31 +965,26 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + [[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" diff --git a/Cargo.toml b/Cargo.toml index 0584fc7..cbae819 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,29 @@ 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" +ctrlc = "3.4.5" +libc = "0.2.158" +linuxfb = { version = "0.3.1", optional = true } +log = { version = "0.4.22", default-features = false } +smithay-client-toolkit = { version = "0.19.2", optional = true, default-features = false } +# compile error otherwise +# steel-core = { path = "../steel/crates/steel-core", optional = true } +marwood = { path = "../marwood/marwood", default-features = false } # , optional = true } swash = "0.1.18" +wayland-protocols-misc = { version = "0.3.3", features = [ + "client", +], optional = true } +toml_edit = { version = "0.22.20" } +# serde_json5 = "0.1.0" +serde = { version = "1.0.209", features = ["derive"] } +env_logger = { version = "0.11.5", default-features = false } + +[features] +default = ["wayland-v2", "framebuffer", "env_logger/auto-color"] # , "marwood"] +framebuffer = ["linuxfb"] +wayland = ["smithay-client-toolkit"] +wayland-v1 = ["wayland"] +wayland-v2 = ["wayland", "wayland-protocols-misc"] +# steel = ["steel-core"] +# marwood = [] +minimal = ["framebuffer"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..78a44cd --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +QPlatformInputContextPlugin +Gtk.IMContext +zwp_input_method_context_v1 +zwp_input_method_v2 +xim + +zwp_text_input_v3? +send backend: zwp_virtual_keyboard_v1? + +## Scheme + +Note: I'm not a Scheme professional, this is an experiment, feel free to +suggest improvements. + +Concepts: + +- Layout - a procedure that returns a UI layout + + +API: + +popup.setLayout(layout: Layout | null) + +keyboard.setLayout(layout: Layout | null) + diff --git a/defs.scm b/defs.scm new file mode 100644 index 0000000..4c7a2ce --- /dev/null +++ b/defs.scm @@ -0,0 +1,30 @@ +; (define (kbd/register cls name func) (display "defining func: ") (display cls) (display " ") (display name) (newline)) +; (define (kbd/class-define name supers) (display "defining class: ") (display name) (display " ") (display supers) (newline)) +; (define (kbd/call cls func-name . args) #f) +; (define (kbd/set-user-data key val) #f) +; (define (kbd/get-user-data key) #f) +; (define (kbd/make-text xy wh text font size) #f) +; (define (kbd/make-rect xy wh [:round-corners 1.0]) #f) +; (define (kbd/make-layout items) #f) +; (define (kbd/width) #f) +; (define (kbd/load-font font-path) #f) + +(define-syntax self (syntax-rules () ((self func arg ...) (kbd/call kbd/this 'func arg ...)))) +(define-syntax super (syntax-rules () ((super func arg ...) (kbd/call (kbd/super kbd/this) 'func arg ...)))) + +(define-syntax kbd/defmember + (syntax-rules () + ((kbd/defmember cls ((input-func-name input-arg ...) input-body ...)) + (begin (define (input-func-name kbd/this input-arg ...) (kbd/call kbd/this 'input-func-name input-arg ...)) + (let ((name 'input-func-name) + (func (lambda (kbd/this input-arg ...) + input-body ...))) + (kbd/register cls name func)))))) + +(define-syntax kbd/defclass + (syntax-rules () + ((kbd/class input-name input-supers input-rest ...) + (begin (kbd/class-define 'input-name 'input-supers) + (kbd/defmember 'input-name input-rest) ...)))) + + diff --git a/shell.nix b/shell.nix index 0718a78..1e70ef2 100644 --- a/shell.nix +++ b/shell.nix @@ -5,4 +5,5 @@ pkgs.mkShell rec { nativeBuildInputs = with pkgs; [ rustc cargo pkg-config ]; buildInputs = with pkgs; [ libxkbcommon ]; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; + LIBCLANG_PATH = "${pkgs.lib.getLib pkgs.libclang}/lib"; } diff --git a/src/framebuffer.rs b/src/framebuffer.rs new file mode 100644 index 0000000..8660830 --- /dev/null +++ b/src/framebuffer.rs @@ -0,0 +1,252 @@ +use std::{ + ffi::CString, + fs::File, + io, + mem::MaybeUninit, + os::{ + fd::{AsRawFd, FromRawFd}, + unix::ffi::OsStrExt, + }, + path::Path, + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; + +use linuxfb::{PixelLayout, PixelLayoutChannel}; + +use crate::{ + image::{self, Argb8888Le, PixelFormat, PixelFormatImpl}, + keyboard::Keyboard, + script::Engine, +}; + +fn map_layout(layout: PixelLayout) -> PixelFormat { + match layout { + PixelLayout { + red: + PixelLayoutChannel { + offset: 16, + length: 8, + msb_right: false, + }, + green: + PixelLayoutChannel { + offset: 8, + length: 8, + msb_right: false, + }, + blue: + PixelLayoutChannel { + offset: 0, + length: 8, + msb_right: false, + }, + alpha: + PixelLayoutChannel { + offset: 24, + length: 8, + msb_right: false, + } + | PixelLayoutChannel { + offset: _, + length: 0, + msb_right: _, + }, + } => PixelFormat::Argb8888Le, + x => panic!("{x:?}"), + } +} + +fn next_frame(_fb: &mut linuxfb::Framebuffer) { + std::thread::sleep(Duration::from_millis(5)); +} + +struct Tty(File); +#[repr(C)] +#[derive(Default)] +struct VtState { + /// active vt + v_active: u16, + /// signal to send + v_signal: u16, + /// vt bitmask + v_state: u16, +} + +const VT_GETSTATE: u16 = 0x5603; + +impl Tty { + pub fn at_path>(p: P) -> io::Result { + let p = CString::new(p.as_ref().as_os_str().as_bytes()) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + unsafe { + let fd = libc::open(p.as_ptr(), libc::O_RDWR | libc::O_NOCTTY); + if fd >= 0 { + Ok(Self(File::from_raw_fd(fd))) + } else { + Err(io::Error::last_os_error()) + } + } + } + pub fn current() -> io::Result { + Self::at_path("/dev/tty0") + } + fn get_size(&self) -> io::Result { + unsafe { + let mut size = MaybeUninit::::zeroed().assume_init(); + if libc::ioctl(self.0.as_raw_fd(), libc::TIOCGWINSZ, &mut size as *mut _) == 0 { + Ok(size) + } else { + Err(io::Error::last_os_error()) + } + } + } + fn set_size(&self, size: libc::winsize) -> io::Result<()> { + unsafe { + if libc::ioctl(self.0.as_raw_fd(), libc::TIOCSWINSZ, &size as *const _) == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + } + fn vt_state(&self) -> io::Result { + let mut stat = VtState::default(); + unsafe { + if libc::ioctl(self.0.as_raw_fd(), VT_GETSTATE.into(), &mut stat) == 0 { + Ok(stat) + } else { + Err(io::Error::last_os_error()) + } + } + } + fn reset(&self) -> io::Result<()> { + let mut left = self.get_size()?; + // make sure min.ws_row != 0 + left.ws_row += 1; + if self.set_size(left).is_err() { + return Ok(()); + } + // double max until we get a failure + let mut right = left; + loop { + right.ws_row *= 2; + if self.set_size(right).is_err() { + break; + } + } + // bin search max size + while left.ws_row + 1 < right.ws_row { + let mut middle = left; + middle.ws_row = (left.ws_row + right.ws_row) / 2; + if self.set_size(middle).is_ok() { + left = middle; + } else { + right = middle; + } + } + Ok(()) + } +} + +// impl Tty + +// fn reset_terminal + +pub struct FbSettings { + pub window_transparency: bool, + pub always_redraw: bool, +} + +pub fn run(mut keyboard: Keyboard, settings: FbSettings) { + static QUIT: AtomicBool = AtomicBool::new(false); + ctrlc::set_handler(|| { + QUIT.store(true, Ordering::Release); + }) + .unwrap(); + let mut fb = linuxfb::Framebuffer::new("/dev/fb0").unwrap(); + // Do any custom setup on the framebuffer here, such as + // setting bytes_per_pixel. + fb.set_bytes_per_pixel(4).unwrap(); + let x = fb.get_pixel_layout(); + let fmt = map_layout(x); + let bpp = fb.get_bytes_per_pixel(); + + let mut buffer = fb.map().unwrap(); + + // These values represent the size of a single buffer, + // which is equivalent to the screen resolution: + let (width, real_height) = fb.get_size(); + let height = real_height as usize - (real_height as usize * 2 / 3); + + let frame: &mut [u8] = buffer.as_mut(); + let frame = &mut frame[width as usize * (real_height as usize * 2 / 3) * bpp as usize..]; + let mut frame2 = vec![0u8; frame.len()].into_boxed_slice(); + let tty = Tty::current().unwrap(); + let mut size = tty.get_size().unwrap(); + // size.ws_row = size.ws_row * 2 / 3; + tty.set_size(size).unwrap(); + while !QUIT.load(Ordering::Acquire) { + next_frame(&mut fb); + let (mut damage, _popup) = keyboard.draw( + &mut frame2, + width, + height as u32, + fmt, + 1, + settings.always_redraw, + ); + if damage.height == 0 { + continue; + } + damage.height = height as u32; + damage.width = width; + damage.top = 0; + damage.left = 0; + if settings.window_transparency { + size.ws_row -= 1; + let _ = tty.set_size(size); + size.ws_row += 1; + let _ = tty.set_size(size); + } + // tty.reset().unwrap(); + for (in_row, out_row) in frame2 + .chunks_exact(width as usize * bpp as usize) + .skip(damage.top.try_into().unwrap_or(0)) + .zip( + frame + .chunks_exact_mut(width as usize * bpp as usize) + .skip(damage.top.try_into().unwrap_or(0)), + ) + .take( + (damage.height as usize).saturating_sub(usize::try_from(-damage.top).unwrap_or(0)), + ) + { + let w = + (damage.width as usize).saturating_sub(usize::try_from(-damage.left).unwrap_or(0)); + let a = damage.left.try_into().unwrap_or(0) * bpp as usize; + let b = a + w * bpp as usize; + unsafe fn handle_fmt(in_row: &[u8], out_row: &mut [u8]) { + for (out_pix, in_pix) in out_row + .chunks_exact_mut(4) + .map(|data| unsafe { &mut *data.as_mut_ptr().cast::() }) + .zip( + in_row + .chunks_exact(4) + .map(|data| unsafe { *data.as_ptr().cast::() }), + ) + { + out_pix.0[3] = 255; + out_pix.blend_u8(in_pix.into()); + } + } + match (settings.window_transparency, fmt) { + (true, PixelFormat::Argb8888Le) => unsafe { + handle_fmt::(in_row, out_row) + }, + _ => out_row[a..b].copy_from_slice(&in_row[a..b]), + } + } + } + tty.reset().unwrap(); +} diff --git a/src/graphics.rs b/src/graphics.rs index 8e2407b..9a43fd9 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,68 +1,58 @@ -use std::{ - borrow::Cow, - collections::{BTreeMap, HashMap}, -}; +use std::{borrow::Cow, collections::BTreeMap}; -use image::Pixel; +use serde::{Deserialize, Serialize}; use swash::{ - scale::{ - image::{Content, Image}, - Render, ScaleContext, Scaler, Source, StrikeWith, - }, + scale::{image::Content, ScaleContext, Scaler}, shape::{Direction, ShapeContext, Shaper}, text::Script, - FontRef, }; -pub struct Canvas<'a> { +use crate::{ + image::{self, ColorRgba}, + text::{FontDb, FontHandle}, +}; + +pub struct LazyCanvas<'a> { + pub(crate) data: &'a mut [u8], pub(crate) width: u32, pub(crate) height: u32, - pub(crate) data: &'a mut [u8], - pub(crate) glyph_cache: HashMap<(FontId, u16), Option>, + pub(crate) fmt: image::PixelFormat, + pub(crate) font_db: &'a mut FontDb, + pub(crate) buffering: u8, } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct FontId(u16); +pub struct Canvas<'a, F: image::PixelFormatImpl> { + pub(crate) image: image::Image<'a, F>, + pub(crate) font_db: &'a mut FontDb, +} -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) { +enum Corner { + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} + +impl<'a, F: image::PixelFormatImpl> Canvas<'a, F> { + pub fn text_size( + &mut self, + shaper: Shaper, + scaler: &mut Scaler, + font_handle: FontHandle, + font_size: f32, + ) -> (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 { + let Some(img2) = self.font_db.render_glyph( + font_handle, + font_size, + glyph.id, + (ret_x.fract(), 0.0f32), + Some(scaler), + ) else { continue; }; ret_y = ret_y.max(img2.placement.top as f32); @@ -70,27 +60,24 @@ impl<'a> Canvas<'a> { }); (ret_x, ret_y) } + #[allow(clippy::too_many_arguments)] pub fn render_text( &mut self, shaper: Shaper, scaler: &mut Scaler, - font: FontId, + font_handle: FontHandle, + font_size: f32, x_pos: f32, mut y_pos: f32, - bounds: Rect, + bounds: Rect, dir: Direction, + col: ColorRgba, ) { - y_pos = self.height as f32 - y_pos; - let max_xpos = bounds.left + bounds.width; - let max_ypos = bounds.bottom + bounds.height; + y_pos = self.image.height as f32 - y_pos; + let max_xpos = bounds.left + i32::try_from(bounds.width).unwrap_or(0); + let max_ypos = bounds.bottom + i32::try_from(bounds.height).unwrap_or(0); 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, @@ -108,15 +95,18 @@ impl<'a> Canvas<'a> { 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 { + let Some(img2) = self.font_db.render_glyph( + font_handle, + font_size, + glyph.id, + (off_x.fract(), (-off_y).fract()), + Some(scaler), + ) else { continue; }; - let (stride, _is_mask) = match img2.content { + let mut off_x = off_x as i32; + let mut off_y = off_y as i32; + let (stride, is_mask) = match img2.content { Content::Mask => (1, true), Content::SubpixelMask => (4, true), Content::Color => (4, false), @@ -128,22 +118,18 @@ impl<'a> Canvas<'a> { } // 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); + off_y -= plc.top; + off_x += 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 max_h = u32::try_from(max_ypos).unwrap_or_default().saturating_sub( + self.image.height.saturating_sub( + u32::try_from(off_y + i32::try_from(plc.height).unwrap_or(0)).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 min_h = u32::try_from(min_ypos).unwrap_or_default().saturating_sub( + self.image.height.saturating_sub( + u32::try_from(off_y + i32::try_from(plc.height).unwrap_or(0)).unwrap_or(0), + ), ); let actual_h = plc.height; let max_w = usize::try_from(max_xpos) @@ -152,17 +138,18 @@ impl<'a> Canvas<'a> { 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 + 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(), + self.image + .rows_mut( + u32::try_from(off_x).unwrap_or(0) + + u32::try_from(min_w).unwrap_or(0), + u32::try_from(off_y).unwrap_or(0) + actual_h.saturating_sub(max_h), ) .take( actual_h @@ -174,120 +161,349 @@ impl<'a> Canvas<'a> { ), ) { - for (pix_in, pix_out) in row_in + for (pix_in, mut 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)), - ) + .zip(row_out.take(max_w.saturating_sub(min_w))) { if stride == 4 { - pix_out - .2 - .blend(&image::Rgba(<[u8; 4]>::try_from(pix_in).unwrap())); + let [r2, g2, b2, a2] = pix_in.try_into().unwrap(); + if is_mask { + let [r1, g1, b1, a1] = ColorRgba::::from(col).0; + pix_out.blend_u8(ColorRgba([ + (r1 * pix_in[0] as f32) as u8, + (g1 * pix_in[1] as f32) as u8, + (b1 * pix_in[2] as f32) as u8, + (a1 * pix_in[3] as f32) as u8, + ])); + } else { + pix_out.blend_u8(ColorRgba([r2, g2, b2, a2])); + } } else { - pix_out.2.blend(&image::Rgba([255, 255, 255, pix_in[0]])); + // mask + let mut col = col; + col.0[3] = (col.0[3] as f32 / u8::MAX as f32 * pix_in[0] as f32) as u8; + pix_out.blend_u8(col); } } } } }); } -} - -#[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() + fn render_quarter_circle( + &mut self, + center_x: i32, + center_y: i32, + radius: u32, + corner: Corner, + col: ColorRgba, + bounds: Rect, + ) { + let ih = self.image.height; + let center_y = i32::try_from(ih).unwrap_or(0) - center_y; + let (start_x, start_y, x1, y1) = match corner { + Corner::TopLeft => (center_x, center_y, false, false), + Corner::TopRight => ( + center_x - i32::try_from(radius).unwrap_or(0), + center_y, + true, + false, + ), + Corner::BottomLeft => ( + center_x, + center_y - i32::try_from(radius).unwrap_or(0), + false, + true, + ), + Corner::BottomRight => ( + center_x - i32::try_from(radius).unwrap_or(0), + center_y - i32::try_from(radius).unwrap_or(0), + true, + true, + ), + }; + for row in self + .image + .rows_mut( + u32::try_from(start_x.max(bounds.left)).unwrap_or(0), + u32::try_from(start_y.max( + i32::try_from(ih).unwrap_or(0) + - bounds.bottom + - i32::try_from(bounds.height).unwrap_or(0), + )) + .unwrap_or(0), + ) + .take( + usize::try_from(radius) + .unwrap() + .saturating_sub( + usize::try_from( + i32::try_from(ih).unwrap_or(0) + - bounds.bottom + - i32::try_from(bounds.height).unwrap_or(0) + - start_y, + ) + .unwrap_or(0), + ) + .min( + bounds.height.saturating_sub( + u32::try_from( + start_y + - (i32::try_from(ih).unwrap_or(0) + - bounds.bottom + - i32::try_from(bounds.height).unwrap_or(0)), + ) + .unwrap_or(0), + ) as usize, + ), + ) + { + for mut pix in row.take( + usize::try_from(radius) + .unwrap() + .saturating_sub(usize::try_from(bounds.left - start_x).unwrap_or(0)) + .min( + usize::try_from( + i32::try_from(bounds.width).unwrap_or(0) - (start_x - bounds.left), + ) + .unwrap_or(0), + ), + ) { + let mut x = pix.x() as i32; + let mut y = pix.y() as i32; + if x1 { + x += 1; + } + if y1 { + y += 1; + } + let dist = ((x.abs_diff(center_x) * x.abs_diff(center_x) + + y.abs_diff(center_y) * y.abs_diff(center_y)) + as f32) + .sqrt(); + if dist > radius as f32 { + continue; + } + let diff = radius as f32 - dist; + if diff >= 1.0 { + pix.blend_u8(col); + } else { + let mut col = col; + col.0[3] = (col.0[3] as f32 * diff) as u8; + pix.blend_u8(col); + } + } + } } - pub fn full() -> Self { - Self::ratio(1.0) + fn render_box(&mut self, mut rect: Rect, col: ColorRgba, bounds: Rect) { + let ih = self.image.height; + let top = + i32::try_from(ih).unwrap_or(0) - rect.bottom - i32::try_from(rect.height).unwrap_or(0); + let top = if top < 0 { + rect.height = rect.height.saturating_sub((-top) as u32); + 0 + } else { + top as u32 + }; + for row in self + .image + .rows_mut( + u32::try_from(rect.left.max(bounds.left)).unwrap_or(0), + top.max(ih - u32::try_from(bounds.bottom).unwrap_or(0) - bounds.height), + ) + .take( + usize::try_from(rect.height) + .unwrap() + .saturating_sub( + usize::try_from( + (ih - u32::try_from(bounds.bottom).unwrap_or(0) - bounds.height) + .saturating_sub(top), + ) + .unwrap_or(0), + ) + .min(bounds.height.saturating_sub(top.saturating_sub( + ih - u32::try_from(bounds.bottom).unwrap_or(0) - bounds.height, + )) as usize), + ) + { + for mut pix in row.take( + usize::try_from(rect.width) + .unwrap() + .saturating_sub( + usize::try_from(bounds.left.saturating_sub(rect.left)).unwrap_or(0), + ) + .min(bounds.width.saturating_sub( + u32::try_from(rect.left.saturating_sub(bounds.left)).unwrap_or(0), + ) as usize), + ) { + pix.blend_u8(col); + } + } } - pub fn ratio(x: f32) -> Self { - Self { - num: 0, - parent_size_sub: 0, - percent_from_parent_size: x, + fn fill_zero(&mut self, bounds: Rect) { + let top = self.image.height - u32::try_from(bounds.bottom).unwrap_or(0) - bounds.height; + for row in self + .image + .rows_mut(u32::try_from(bounds.left).unwrap_or(0), top) + .take(bounds.height as usize) + { + for mut pix in row.take(bounds.width as usize) { + pix.set_u8(ColorRgba([0, 0, 0, 0])); + } } } } -pub trait Widget<'a> { +pub trait Widget { /// render with x/y as the bottom left corner - fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect); + fn render(&mut self, canvas: &mut Canvas, bounds: Rect); +} +pub trait LazyWidget { + /// render with x/y as the bottom left corner + fn render(&mut self, canvas: &mut LazyCanvas, bounds: Rect); +} +impl LazyWidget for T { + fn render(&mut self, canvas: &mut LazyCanvas, bounds: Rect) { + match canvas.fmt { + image::PixelFormat::Argb8888Le => Widget::render( + self, + &mut Canvas { + image: image::Image::::new( + canvas.data, + canvas.width, + canvas.height, + ), + font_db: canvas.font_db, + }, + bounds, + ), + } + } } -#[derive(Default)] -pub struct Layout<'a> { +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub enum WidgetEnum { + Zero(Zero), + Shape(SimpleShape), + Text(Text), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct Child { + bounds: Option>, + widget: Box<[WidgetEnum]>, + dirty: u8, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Layout { #[allow(clippy::type_complexity)] - children: BTreeMap<(i32, String), (Rect, Rect, Box>)>, - z_levels: BTreeMap, + children: BTreeMap, } -#[derive(Copy, Clone, Default)] -pub struct Rect { +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct Rect { pub left: T, pub bottom: T, - pub width: T, - pub height: T, + pub width: Y, + pub height: Y, } -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, Y: Copy> Rect { + fn h(&self) -> T { + T::try_from(self.height).unwrap_or_default() + } + fn w(&self) -> T { + T::try_from(self.width).unwrap_or_default() } } -impl<'a> Layout<'a> { - pub fn add_child( +impl Layout { + pub fn new() -> Self { + Self { + children: BTreeMap::new(), + } + } + pub fn add_child(&mut self, widget: Vec, id: &str, bounds: Option>) { + self.children.insert( + id.to_owned(), + Child { + bounds, + widget: widget.into_boxed_slice(), + dirty: 0, + }, + ); + } + pub fn set_dirty(&mut self, id: &str, v: u8) { + let Some(x) = self.children.get_mut(id) else { + return; + }; + x.dirty = v; + } + pub fn all_set_dirty(&mut self, v: u8) { + for x in &mut self.children.values_mut() { + x.dirty = v; + } + } +} + +impl Widget for WidgetEnum { + fn render( &mut self, - widget: Box>, - z: i32, - id: &str, - bounds: Rect, + canvas: &mut Canvas, + 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); + match self { + Self::Zero(x) => Widget::render(x, canvas, bounds), + Self::Shape(x) => Widget::render(x, canvas, bounds), + Self::Text(x) => Widget::render(x, canvas, bounds), } } } -#[derive(Default)] +impl Layout { + pub fn render(&mut self, canvas: &mut LazyCanvas, bounds: Rect) -> Rect { + let mut ret = None; + let mut tmp = Child { + bounds: bounds.into(), + widget: Box::new([WidgetEnum::Zero(Zero)]), + dirty: 0, + }; + let mut always_redraw = vec![&mut tmp]; + // TODO: fix clipping and optimizations + // canvas.data.fill(0); + for (name, child) in self.children.iter_mut() { + if child.dirty == 0 { + if name.starts_with('!') { + always_redraw.push(child); + } + continue; + } + child.dirty -= 1; + let bounds_resolved = child.bounds.unwrap_or(bounds); + for child in always_redraw.drain(..).chain(std::iter::once(child)) { + for w in child.widget.iter_mut() { + LazyWidget::render(w, canvas, bounds_resolved); + } + } + if ret.is_some() { + ret = Some(bounds); + } else { + ret = Some(bounds_resolved); + } + } + ret.unwrap_or(Rect { + bottom: 0, + left: 0, + width: 0, + height: 0, + }) + } +} + +#[allow(dead_code)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)] pub enum Alignment { #[default] Start, @@ -295,22 +511,34 @@ pub enum Alignment { End, } -pub struct Text<'a> { - pub text: Cow<'a, str>, - pub font: FontRef<'a>, - pub font_id: u16, +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct Text { + pub text: Cow<'static, str>, + pub font_handle: FontHandle, pub font_size: f32, + #[serde(with = "crate::serde::script")] pub script: Script, + #[serde(with = "crate::serde::direction")] pub direction: Direction, pub halign: Alignment, pub valign: Alignment, + pub color: image::ColorRgba, + pub pos: Rect, } -impl<'a> Widget<'a> for Text<'a> { - fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect) { +impl Widget for Text { + fn render( + &mut self, + canvas: &mut Canvas, + bounds: Rect, + ) { let mut context = ShapeContext::new(); + let Some(font) = canvas.font_db.get_font(self.font_handle) else { + log::warn!("font not found: {:?}", self.font_handle); + return; + }; let mut shaper = context - .builder(self.font) + .builder(font) .script(self.script) .direction(self.direction) .size(self.font_size) @@ -318,47 +546,153 @@ impl<'a> Widget<'a> for Text<'a> { shaper.add_str(&self.text); let mut context2 = ScaleContext::new(); let mut scaler = context2 - .builder(self.font) + .builder(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_w, mut _text_h) = + canvas.text_size(shaper, &mut scaler, self.font_handle, self.font_size); let text_h = self.font_size / 1.2; shaper = context - .builder(self.font) + .builder(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; + let mut x = self.pos.left as i32; + let mut y = self.pos.bottom as i32; match self.halign { Alignment::End => { - x += bounds.width as i32 - text_w as i32; + x += self.pos.width as i32 - text_w as i32; } Alignment::Middle => { - x += ((bounds.width as f32 - text_w) / 2.0) as i32; + x += ((self.pos.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; + y += self.pos.height as i32 - text_h as i32; } Alignment::Middle => { - y += ((bounds.height as f32 - text_h) / 2.0) as i32; + y += ((self.pos.height as f32 - text_h) / 2.0) as i32; } Alignment::Start => {} } canvas.render_text( shaper, &mut scaler, - FontId(self.font_id), + self.font_handle, + self.font_size, x as f32, y as f32, bounds, self.direction, + self.color, ); } } + +#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] +pub struct SimpleShape { + /// 1.0: circle, 0.0: square + pub(crate) round_corners_ratio: f32, + pub(crate) col: ColorRgba, + pub(crate) pos: Rect, +} + +impl Widget for SimpleShape { + fn render( + &mut self, + canvas: &mut Canvas, + bounds: Rect, + ) { + let pos = self.pos; + if self.round_corners_ratio <= f32::EPSILON { + canvas.render_box(pos, self.col, bounds); + } else { + // draw a cross and 4 corners + // /-\ + // --- + // \-/ + let radius = + ((pos.width.min(pos.height) as f32) * self.round_corners_ratio / 2.) as u32; + canvas.render_box( + Rect { + bottom: pos.bottom + i32::try_from(radius).unwrap_or(0), + left: pos.left, + width: pos.width, + height: pos.height.saturating_sub(radius * 2), + }, + self.col, + bounds, + ); + canvas.render_box( + Rect { + bottom: pos.bottom, + left: pos.left + i32::try_from(radius).unwrap_or(0), + width: pos.width.saturating_sub(radius * 2), + height: radius, + }, + self.col, + bounds, + ); + canvas.render_box( + Rect { + bottom: pos.bottom + i32::try_from(pos.height).unwrap_or(0) + - i32::try_from(radius).unwrap_or(0), + left: pos.left + i32::try_from(radius).unwrap_or(0), + width: pos.width.saturating_sub(radius * 2), + height: radius, + }, + self.col, + bounds, + ); + canvas.render_quarter_circle( + pos.left + i32::try_from(radius).unwrap_or(0), + pos.bottom + i32::try_from(radius).unwrap_or(0), + radius, + Corner::TopRight, + self.col, + bounds, + ); + canvas.render_quarter_circle( + pos.left + pos.w() - i32::try_from(radius).unwrap_or(0), + pos.bottom + i32::try_from(radius).unwrap_or(0), + radius, + Corner::TopLeft, + self.col, + bounds, + ); + canvas.render_quarter_circle( + pos.left + i32::try_from(radius).unwrap_or(0), + pos.bottom + pos.h() - i32::try_from(radius).unwrap_or(0), + radius, + Corner::BottomRight, + self.col, + bounds, + ); + canvas.render_quarter_circle( + pos.left + pos.w() - i32::try_from(radius).unwrap_or(0), + pos.bottom + pos.h() - i32::try_from(radius).unwrap_or(0), + radius, + Corner::BottomLeft, + self.col, + bounds, + ); + } + } +} + +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct Zero; +impl Widget for Zero { + fn render( + &mut self, + canvas: &mut Canvas, + bounds: Rect, + ) { + canvas.fill_zero(bounds); + } +} diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..a2d063d --- /dev/null +++ b/src/image.rs @@ -0,0 +1,298 @@ +use std::{marker::PhantomData, mem, slice::ChunksExactMut}; + +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum PixelFormat { + Argb8888Le, +} + +#[repr(transparent)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +pub struct ColorRgba(pub [T; 4]); + +impl From> for ColorRgba { + #[inline(always)] + fn from(value: ColorRgba) -> Self { + let [r, g, b, a] = value.0.map(|x| x as f32 / u8::MAX as f32); + Self([r, g, b, a]) + } +} + +/// # Safety +/// +/// The type must be [u8; BYTES] +pub unsafe trait PixelFormatImpl: + Sized + + Copy + + Clone + + From> + + From> + + Into> + + Into> +{ + fn blend_f32(&mut self, color: ColorRgba) { + let [r, g, b, a] = color.0; + let [bg_r, bg_g, bg_b, bg_a] = Into::>::into(*self).0; + let out_a = bg_a + a - bg_a * a; + if out_a <= f32::EPSILON { + return; + }; + let [out_r, out_g, out_b] = [(r, bg_r), (g, bg_g), (b, bg_b)] + .map(|(fg, bg)| (fg * a + bg * bg_a * (1.0 - a)) / out_a); + *self = ColorRgba([out_r, out_g, out_b, out_a]).into(); + } + fn blend_u8(&mut self, color: ColorRgba) { + let [fg_r, fg_g, fg_b, fg_a] = color.0; + if fg_a == 0 { + return; + } + if fg_a == 255 { + *self = color.into(); + return; + } + let [bg_r, bg_g, bg_b, bg_a] = Into::>::into(*self).0; + let out_a = (fg_a as u32 + bg_a as u32) * 255 - (fg_a as u32 * bg_a as u32); + let f1 = fg_a as u32 * 255; + let f2 = bg_a as u32 * (255 - fg_a) as u32; + let r = (((fg_r as u32 * f1) + (bg_r as u32 * f2)) / out_a) as u8; + let g = (((fg_g as u32 * f1) + (bg_g as u32 * f2)) / out_a) as u8; + let b = (((fg_b as u32 * f1) + (bg_b as u32 * f2)) / out_a) as u8; + *self = ColorRgba([r, g, b, (out_a / 255) as u8]).into(); + } +} + +#[repr(transparent)] +#[derive(Copy, Clone, Debug)] +pub struct Argb8888Le(pub [u8; 4]); +impl From> for Argb8888Le { + #[inline(always)] + fn from(value: ColorRgba) -> Self { + let [r, g, b, a] = value.0; + Self([b, g, r, a]) + } +} +impl From> for Argb8888Le { + #[inline(always)] + fn from(value: ColorRgba) -> Self { + let [r, g, b, a] = value.0.map(|x| (u8::MAX as f32 * x) as u8); + Self([b, g, r, a]) + } +} +impl From for ColorRgba { + #[inline(always)] + fn from(value: Argb8888Le) -> Self { + let [b, g, r, a] = value.0; + Self([r, g, b, a]) + } +} +impl From for ColorRgba { + #[inline(always)] + fn from(value: Argb8888Le) -> Self { + let [b, g, r, a] = value.0.map(|x| x as f32 / u8::MAX as f32); + Self([r, g, b, a]) + } +} +// from https://github.com/libsdl-org/SDL/blob/d9a5ed75b969af481a3da882ca87def205499513/src/video/SDL_blit.h#L545 +// this basically does src * factor + dst * (1 - factor) for each byte in src/dst +#[allow(unused)] +fn factor_blend(mut src: u64, mut dst: u64, factor: u64) -> u32 { + src = (src | (src << 24)) & 0x00FF00FF00FF00FF; + dst = (dst | (dst << 24)) & 0x00FF00FF00FF00FF; + dst = ((src - dst) * factor) + (dst << 8) - dst; + dst += 0x0001000100010001; + dst += (dst >> 8) & 0x00FF00FF00FF00FF; + dst &= 0xFF00FF00FF00FF00; + ((dst >> 8) | (dst >> 32)) as u32 +} +unsafe impl PixelFormatImpl for Argb8888Le { + /* + this could potentially be better on some hardware, but on mine it's just slower + fn blend_u8(&mut self, color: ColorRgba) { + let [fg_r, fg_g, fg_b, fg_a] = color.0; + if fg_a == 0 { + return; + } + if fg_a == 255 { + *self = color.into(); + return; + } + let [bg_b, bg_g, bg_r, bg_a] = self.0; + let src = u32::from_le_bytes([fg_b, fg_g, fg_r, 255]); + let dst = u32::from_le_bytes([ + fast_mul(bg_a, bg_b), + fast_mul(bg_a, bg_g), + fast_mul(bg_a, bg_r), + bg_a, + ]); + let [b, g, r, out_a] = factor_blend(src.into(), dst.into(), fg_a.into()).to_le_bytes(); + if out_a == 0 { + return; + } + let r = fast_div(r, out_a); + let g = fast_div(g, out_a); + let b = fast_div(b, out_a); + self.0 = [b, g, r, out_a]; + }*/ + fn blend_u8(&mut self, color: ColorRgba) { + let [fg_r, fg_g, fg_b, fg_a] = color.0.map(u32::from); + if fg_a == 0 { + return; + } + if fg_a == 255 { + *self = color.into(); + return; + } + let [bg_b, bg_g, bg_r, bg_a] = self.0.map(u32::from); + let a = (fg_a + bg_a) * 255 - (fg_a * bg_a); + let f1 = fg_a * 255; + let f2 = bg_a * (255 - fg_a); + let r = (((fg_r * f1) + (bg_r * f2)) / a) as u8; + let g = (((fg_g * f1) + (bg_g * f2)) / a) as u8; + let b = (((fg_b * f1) + (bg_b * f2)) / a) as u8; + self.0 = [b, g, r, (a / 255) as u8]; + } +} + +pub struct Image<'a, F: PixelFormatImpl> { + pub(crate) buffer: &'a mut [u8], + pub(crate) width: u32, + pub(crate) height: u32, + _ph: PhantomData, +} + +impl<'a, F: PixelFormatImpl> Image<'a, F> { + pub fn new(buffer: &'a mut [u8], width: u32, height: u32) -> Self { + assert_eq!(mem::align_of::(), mem::align_of::()); + Self { + buffer, + width, + height, + _ph: PhantomData, + } + } + pub fn rows_mut(&mut self, start_x: u32, start_y: u32) -> RowsIter<'_, F> { + if start_y >= self.height || start_x >= self.width { + return RowsIter:: { + iter: self.buffer[..0].chunks_exact_mut(1), + y: 0, + start_x: usize::try_from(start_x).unwrap(), + _ph: self._ph, + }; + } + RowsIter:: { + iter: self.buffer[usize::try_from(start_y).unwrap() + * usize::try_from(self.width).unwrap() + * mem::size_of::()..] + .chunks_exact_mut(usize::try_from(self.width).unwrap() * mem::size_of::()), + y: usize::try_from(start_y).unwrap(), + start_x: usize::try_from(start_x).unwrap(), + _ph: self._ph, + } + } +} + +pub struct RowsIter<'a, F: PixelFormatImpl> { + iter: ChunksExactMut<'a, u8>, + y: usize, + start_x: usize, + _ph: PhantomData, +} + +pub struct Row<'a, F: PixelFormatImpl> { + y: usize, + iter: ChunksExactMut<'a, u8>, + x: usize, + _ph: PhantomData, +} + +impl<'a, F: PixelFormatImpl> Iterator for RowsIter<'a, F> { + type Item = Row<'a, F>; + fn next(&mut self) -> Option { + let data = self.iter.next()?; + let ret = Row { + y: self.y, + iter: data[self.start_x * mem::size_of::()..].chunks_exact_mut(mem::size_of::()), + x: self.start_x, + _ph: self._ph, + }; + self.y += 1; + Some(ret) + } +} + +impl<'a, F: 'a + PixelFormatImpl> Iterator for Row<'a, F> { + type Item = Pixel<'a, F>; + fn next(&mut self) -> Option { + let data = self.iter.next()?; + let ret = Pixel { + data: unsafe { &mut *data.as_mut_ptr().cast() }, + x: self.x, + y: self.y, + }; + self.x += 1; + Some(ret) + } +} + +pub struct Pixel<'a, F: PixelFormatImpl> { + pub(crate) data: &'a mut F, + pub(crate) x: usize, + pub(crate) y: usize, +} + +#[allow(unused)] +#[inline(always)] +pub fn fast_mul(a: u8, b: u8) -> u8 { + /*let mut a = u32::from(a); + a |= a << 8; + a *= u32::from(b); + a += 0x8080; + a >>= 16; + a as u8*/ + ((u16::from(a) * u16::from(b)) / 255) as u8 +} + +#[inline(always)] +#[allow(unused)] +fn fast_div(a: u8, b: u8) -> u8 { + ((a as u16 * 255) / b as u16) as u8 +} +#[inline(always)] +#[allow(unused)] +fn f2u(f: f32) -> u8 { + (f * u8::MAX as f32) as u8 +} +#[inline(always)] +#[allow(unused)] +fn u2f(u: u8) -> f32 { + u as f32 / u8::MAX as f32 +} + +#[allow(unused)] +impl<'a, F: PixelFormatImpl> Pixel<'a, F> { + pub fn x(&self) -> usize { + self.x + } + pub fn y(&self) -> usize { + self.y + } + pub fn set_u8(&mut self, color: ColorRgba) { + *self.data = color.into(); + } + pub fn rgba_f32(&self) -> ColorRgba { + (*self.data).into() + } + pub fn rgba_u8(&self) -> ColorRgba { + (*self.data).into() + } + pub fn set_f32(&mut self, color: ColorRgba) { + *self.data = color.into(); + } + pub fn blend_u8(&mut self, color: ColorRgba) { + self.data.blend_u8(color); + } + pub fn blend_f32(&mut self, color: ColorRgba) { + self.data.blend_f32(color); + } +} diff --git a/src/keyboard.rs b/src/keyboard.rs index 4ebcf43..9596b63 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,85 +1,107 @@ -use swash::{shape::Direction, text::Script, FontRef}; +use std::{collections::HashMap, time::Instant}; -use crate::graphics::{self, Canvas, Widget}; +use crate::{ + graphics::{self, LazyCanvas}, + image::PixelFormat, + script::{Engine, Value}, + text::FontDb, +}; -pub struct Keyboard<'a> { - latin_font: FontRef<'a>, - emoji_font: FontRef<'a>, - arabic_font: FontRef<'a>, +pub struct Popup { + left: i32, + top: i32, width: u32, height: u32, - shift: u32, + fmt: PixelFormat, + layout: graphics::Layout, + is_first_draw: bool, +} +pub struct PopupInfo<'b> { + popup: &'b mut Popup, + font_db: &'b mut FontDb, } -impl<'a> Keyboard<'a> { - pub fn new(latin_font: FontRef<'a>, emoji_font: FontRef<'a>, arabic_font: FontRef<'a>) -> Self { +pub struct Class { + pub supers: Vec, + pub methods: HashMap, +} + +impl Default for Class { + fn default() -> Self { Self { - latin_font, - emoji_font, - arabic_font, - width: 720, - height: 480, - shift: 0, + supers: vec![], + methods: HashMap::new(), } } - pub fn size(&self) -> (u32, u32) { - (self.width, self.height) +} + +pub struct Keyboard { + pub(crate) layout: Option, + popup: Option, + pub(crate) width: u32, + height: u32, + fmt: PixelFormat, + pub(crate) font_db: FontDb, + is_first_draw: bool, + config_handlers: HashMap< + String, + Vec<( + usize, + Box, + )>, + >, + timers: Vec<(Instant, Box)>, + config_handler_id: usize, + pub(crate) user_data: HashMap>, + pub(crate) classes: HashMap>, + pub(crate) env: Option>, +} + +impl<'b> PopupInfo<'b> { + pub fn width(&self) -> u32 { + self.popup.width } - pub fn set_size(&mut self, width: u32, height: u32) { - self.width = width; - self.height = height; + pub fn height(&self) -> u32 { + self.popup.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, + pub fn top(&self) -> i32 { + self.popup.top + } + pub fn left(&self) -> i32 { + self.popup.left + } + pub fn draw( + &mut self, + canvas: &mut [u8], + width: u32, + height: u32, + fmt: PixelFormat, + buffering: u8, + redraw: bool, + ) -> DamageInfo { + if self.popup.width != width + || self.popup.height != height + || self.popup.fmt != fmt + || self.popup.is_first_draw + || redraw + { + self.popup.is_first_draw = false; + self.popup.layout.all_set_dirty(buffering); + } + self.popup.width = width; + self.popup.height = height; + self.popup.fmt = fmt; + let width = self.popup.width; + let height = self.popup.height; + let mut canvas = LazyCanvas { data: canvas, - glyph_cache: Default::default(), + width, + height, + fmt: self.popup.fmt, + font_db: self.font_db, + buffering, }; - layout.render( + let dmg = self.popup.layout.render( &mut canvas, graphics::Rect { left: 0, @@ -88,32 +110,254 @@ impl<'a> Keyboard<'a> { 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; + DamageInfo { + height: dmg.height, + width: dmg.width, + top: height as i32 - dmg.bottom - dmg.height as i32, + left: dmg.left, + } } } + +#[derive(Copy, Clone, Debug)] +pub struct DamageInfo { + pub left: i32, + pub top: i32, + pub width: u32, + pub height: u32, +} + +impl Default for Keyboard { + fn default() -> Self { + Self::new() + } +} + +impl Keyboard { + pub fn new() -> Self { + Self { + width: 720, + height: 480, + font_db: Default::default(), + fmt: PixelFormat::Argb8888Le, + layout: None, + popup: None, + is_first_draw: true, + config_handlers: HashMap::new(), + config_handler_id: 0, + timers: Vec::new(), + user_data: HashMap::new(), + classes: HashMap::new(), + env: None, + } + // ret.layout = Some(ret.create_layout()); + // ret + } + pub fn size(&self) -> (u32, u32) { + (self.width, self.height) + } + pub fn show(&mut self) { + /*self.popup = Some(Popup { + left: 0, + top: 0, + width: 100, + height: 100, + fmt: self.fmt, + layout: { + let mut layout = graphics::Layout::default(); + layout.add_child( + [Box::new(graphics::SimpleShape { + col: ColorRgba([35, 16, 26, 192]), + round_corners_ratio: 1.0, + pos: graphics::Rect { + left: Size::zero(), + bottom: Size::zero(), + width: Size::x_ratio(1.0), + height: Size::y_ratio(1.0), + }, + })], + "", + graphics::Rect { + left: Size::zero(), + bottom: Size::zero(), + width: Size::x_ratio(1.0), + height: Size::y_ratio(1.0), + }, + ); + layout + }, + is_first_draw: true, + });*/ + } + pub fn _add_settings_handler( + &mut self, + key: &str, + handler: impl 'static + FnMut(&Self, &str, &toml_edit::Value), + ) -> usize { + let id = self.config_handler_id; + self.config_handler_id += 1; + self.config_handlers + .entry(key.to_owned()) + .or_default() + .push((id, Box::new(handler))); + id + } + pub fn _remove_settings_handler(&mut self, key: &str, id: usize) { + if let Some(x) = self.config_handlers.get_mut(key) { + x.retain(|(i, _)| id != *i); + } + } + pub fn hide(&mut self) { + self.popup = None; + } + /// Returns: popup to draw, if any + pub fn draw<'c, 'd>( + &'c mut self, + canvas: &mut [u8], + width: u32, + height: u32, + fmt: PixelFormat, + buffering: u8, + redraw: bool, + ) -> (DamageInfo, Option>) + where + 'c: 'd, + { + if self.width != width + || self.height != height + || self.fmt != fmt + || self.is_first_draw + || redraw + { + if let Some(func) = self + .classes + .get_mut("en") + .and_then(|cls| cls.methods.get_mut("kbd/width-changed")) + .cloned() + { + let mut env = self.env.take().unwrap(); + env.call(self, &func, vec![Value::Symbol("en".into())]) + .unwrap(); + self.env = Some(env); + } + self.is_first_draw = false; + if let Some(layout) = &mut self.layout { + layout.all_set_dirty(buffering); + } + } + self.width = width; + self.height = height; + self.fmt = fmt; + let width = self.width; + let height = self.height; + let mut canvas = LazyCanvas { + data: canvas, + width, + height, + fmt: self.fmt, + font_db: &mut self.font_db, + buffering, + }; + let dmg = self + .layout + .as_mut() + .map(|layout| { + layout.render( + &mut canvas, + graphics::Rect { + left: 0, + bottom: 0, + width, + height, + }, + ) + }) + .unwrap_or_default(); + ( + DamageInfo { + height: dmg.height, + width: dmg.width, + top: height as i32 - dmg.bottom - dmg.height as i32, + left: dmg.left, + }, + self.popup.as_mut().map(|popup| PopupInfo { + popup, + font_db: &mut self.font_db, + }), + ) + } + /*#[allow(unused)] + fn create_layout<'b>(&mut self) -> graphics::Layout { + self.font_db + .load_font("/home/user/.nix-profile/share/fonts/noto/NotoSans[wdth,wght].ttf") + .unwrap(); + self.font_db + .load_font("/home/user/.nix-profile/share/fonts/noto/NotoColorEmoji.ttf") + .unwrap(); + self.font_db + .load_font("/home/user/.nix-profile/share/fonts/noto/NotoSansArabic[wdth,wght].ttf") + .unwrap(); + let latin_font = self.font_db.font_handle("Noto Sans").unwrap(); + let emoji_font = self.font_db.font_handle("Noto Color Emoji").unwrap(); + let arabic_font = self.font_db.font_handle("Noto Sans Arabic").unwrap(); + let mut layout = graphics::Layout::default(); + layout.add_child( + [Box::new(graphics::SimpleShape { + col: ColorRgba([128, 128, 128, 127]), + round_corners_ratio: 0.5, + pos: graphics::Rect { + left: graphics::Size::val(0), + bottom: graphics::Size::val(0), + width: graphics::Size::x_ratio(1.0), + height: graphics::Size::y_ratio(1.0), + }, + })], + "!bg", + graphics::Rect { + left: graphics::Size::zero(), + bottom: graphics::Size::zero(), + width: graphics::Size::x_ratio(1.0), + height: graphics::Size::y_ratio(1.0), + }, + ); + layout.add_child( + [Box::new(graphics::Text { + text: "عَرَبِيّ".into(), + font_handle: arabic_font, + halign: graphics::Alignment::Middle, + valign: graphics::Alignment::Middle, + direction: Direction::RightToLeft, + font_size: 128.0, + script: Script::Arabic, + color: ColorRgba([0, 0, 0, 255]), + })], + "a", + graphics::Rect { + left: graphics::Size::zero(), + bottom: graphics::Size::y_ratio(0.0), + width: graphics::Size::x_ratio(0.5), + height: graphics::Size::y_ratio(1.0), + }, + ); + layout.add_child( + [Box::new(graphics::Text { + text: "🔥🗣️".into(), + font_handle: emoji_font, + halign: graphics::Alignment::Middle, + valign: graphics::Alignment::Middle, + direction: Direction::LeftToRight, + font_size: 128.0, + script: Script::Latin, + color: ColorRgba([0, 0, 0, 255]), + })], + "b", + graphics::Rect { + left: graphics::Size::x_ratio(0.5), + bottom: graphics::Size::y_ratio(0.0), + width: graphics::Size::x_ratio(0.5), + height: graphics::Size::y_ratio(1.0), + }, + ); + layout + }*/ +} diff --git a/src/main.rs b/src/main.rs index 279f7cb..9a21b90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,536 @@ -use std::{fs, io, path::Path}; +#![allow(clippy::type_complexity)] +use std::{cell::RefCell, rc::Rc}; + +use script::{Engine, ErrorTrait, Func, Value}; +#[cfg(feature = "wayland")] +pub use wayland_protocols_misc; use keyboard::Keyboard; -use swash::FontRef; +#[cfg(feature = "framebuffer")] +mod framebuffer; mod graphics; +mod image; mod keyboard; +mod script; +mod serde; +mod text; +#[cfg(feature = "wayland")] 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")) +#[derive(Default)] +struct ScriptEnv(E, Rc>>); + +impl ScriptEnv { + fn eval(&mut self, kbd: &mut Keyboard, text: &str) -> Result, E::Error> { + std::mem::swap(kbd, &mut (*self.1).borrow_mut()); + let ret = self.0.eval_t(text); + std::mem::swap(kbd, &mut (*self.1).borrow_mut()); + ret + } + fn call( + &mut self, + kbd: &mut Keyboard, + func: &E::Func, + args: Vec>, + ) -> Result, E::Error> { + std::mem::swap(kbd, &mut (*self.1).borrow_mut()); + // work with kbd1 + let ret = func.call(&mut self.0, args); + std::mem::swap(kbd, &mut (*self.1).borrow_mut()); + ret + } } -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); +fn new_script_env() -> ScriptEnv { + let mut engine = script::create_engine(); + const DEFS: &str = include_str!("../defs.scm"); + let ctx: Rc>> = Rc::default(); + engine.eval_t(DEFS).unwrap(); + engine.gc(); + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/register", + Box::new(move |vm, args| { + let Ok::<[Value<_>; 3], _>([cls, name, func]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/register")); + }; + let Value::Symbol(cls) = cls else { + return Err(ErrorTrait::invalid_argt( + "kbd/register", + "symbol", + &format!("{cls:?}"), + )); + }; + let Value::Symbol(name) = name else { + return Err(ErrorTrait::invalid_argt( + "kbd/register", + "symbol", + &format!("{name:?}"), + )); + }; + let Value::Func(mut func) = func else { + return Err(ErrorTrait::invalid_argt( + "kbd/register", + "function", + &format!("{func:?}"), + )); + }; + func.add_ref(vm); + println!("register {cls:?} {name:?} {func:?}"); + (*ctx1) + .borrow_mut() + .classes + .entry(cls.into_owned()) + .or_default() + .methods + .insert(name.into_owned(), func); + println!("done"); + + Ok(Value::Null) + }), + ); + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/class-define", + Box::new(move |_vm, args| { + let Ok::<[Value<_>; 2], _>([cls, supers]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/class-define")); + }; + let Value::Symbol(cls) = cls else { + return Err(ErrorTrait::invalid_argt( + "kbd/class-define", + "symbol", + &format!("{cls:?}"), + )); + }; + let supers = match supers { + Value::Null => vec![], + Value::RevArray(supers) => supers, + _ => { + return Err(ErrorTrait::invalid_argt( + "kbd/class-define", + "list", + &format!("{supers:?}"), + )); + } + }; + let supers = supers + .into_iter() + .rev() + .map(|sup| { + let Value::Symbol(sup) = sup else { + return Err(ErrorTrait::invalid_argt( + "kbd/class-define", + "list of symbols", + &format!("list containing {sup:?}"), + )); + }; + Ok(sup.into_owned()) + }) + .collect::, _>>()?; + println!("register {cls:?} {supers:?}"); + + (*ctx1) + .borrow_mut() + .classes + .entry(cls.into_owned()) + .or_default() + .supers + .extend(supers); + println!("done"); + Ok(Value::Null) + }), + ); + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/call", + Box::new(move |vm, mut args| { + args.reverse(); + let cls = args + .pop() + .ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?; + let name = args + .pop() + .ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?; + let Value::Symbol(cls) = cls else { + return Err(ErrorTrait::invalid_argt( + "kbd/call", + "symbol", + &format!("{cls:?}"), + )); + }; + let Value::Symbol(name) = name else { + return Err(ErrorTrait::invalid_argt( + "kbd/call", + "symbol", + &format!("{name:?}"), + )); + }; + fn call_func( + func: &mut E::Func, + engine: &mut E, + args: Vec>, + ) -> Result, E::Error> { + func.call(engine, args) + } + let func = (*ctx1) + .borrow_mut() + .classes + .get_mut(cls.as_ref()) + .and_then(|cls| cls.methods.get_mut(name.as_ref()).cloned()); + if let Some(mut func) = func { + args.push(Value::Symbol(cls)); + args.reverse(); + call_func(&mut func, vm, args) + } else { + Err(ErrorTrait::from_str( + "kbd/call", + &format!("{cls}/{name} is not defined"), + )) + } + }), + ); + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/set-user-data", + Box::new(move |vm, args| { + let Ok::<[Value<_>; 2], _>([k, mut v]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/set-user-data")); + }; + let Value::Symbol(k) = k else { + return Err(ErrorTrait::invalid_argt( + "kbd/set-user-data", + "symbol", + &format!("{k:?}"), + )); + }; + println!("setting user data {k:?} to {v:?}"); + if let Some(mut val) = (*ctx1) + .borrow_mut() + .user_data + .insert(k.into_owned(), v.clone()) + { + // FIXME: theoretically this can unref data that's referenced elsewhere + val.unref(vm); + } + v.add_ref(vm); + Ok(Value::Null) + }), + ); + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/get-user-data", + Box::new(move |_vm, args| { + let Ok::<[Value<_>; 1], _>([k]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/get-user-data")); + }; + let Value::Symbol(k) = k else { + return Err(ErrorTrait::invalid_argt( + "kbd/get-user-data", + "symbol", + &format!("{k:?}"), + )); + }; + println!("getting user data {k:?}"); + Ok((*ctx1) + .borrow() + .user_data + .get(k.as_ref()) + .cloned() + .unwrap_or(Value::Bool(false))) + }), + ); + fn add_to_i64(name: &str, a: Value, b: Value) -> Result { + Ok(match (a, b) { + (Value::Int(a), Value::Int(b)) => a.saturating_add(b), + (Value::Float(a), Value::Float(b)) => (a + b) as i64, + (Value::Int(a), Value::Float(b)) => (a as f64 + b) as i64, + (Value::Float(a), Value::Int(b)) => (a + b as f64) as i64, + x => { + return Err(E::Error::invalid_argt( + name, + "int or float", + &format!("{:?}", x.1), + )) + } + }) + } + fn to_i64(name: &str, x: Value) -> Result { + add_to_i64(name, x, Value::Int(0)) + } + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/make-text", + Box::new(move |_vm, args| { + let Ok::<[Value<_>; 5], _>([xy, wh, text, font, font_size]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/make-text")); + }; + let Value::Pair(xy) = xy else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-text", + "pair", + &format!("{xy:?}"), + )); + }; + let Value::Pair(wh) = wh else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-text", + "pair", + &format!("{wh:?}"), + )); + }; + let Value::String(text) = text else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-text", + "string", + &format!("{text:?}"), + )); + }; + let Value::String(font) = font else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-text", + "string", + &format!("{font:?}"), + )); + }; + let font_size = match font_size { + Value::Int(x) => x as f64, + Value::Float(x) => x, + _ => { + return Err(ErrorTrait::invalid_argt( + "kbd/make-text", + "string", + &format!("{font_size:?}"), + )) + } + }; + let Some(font_handle) = ctx1.borrow_mut().font_db.font_handle(&font) else { + return Err(ErrorTrait::from_str("kbd/make-text", "font not found")); + }; + let (x, y) = *xy; + let (w, h) = *wh; + let x1 = to_i64("kbd/make-text", x.clone())?; + let y1 = to_i64("kbd/make-text", y.clone())?; + let x_w = add_to_i64("kbd/make-text", x, w)?; + let y_h = add_to_i64("kbd/make-text", y, h)?; + let (x, y) = (x1, y1); + let (w, h) = (x_w - x, y_h - y); + let halign = graphics::Alignment::Middle; + let valign = graphics::Alignment::Middle; + let direction = swash::shape::Direction::LeftToRight; + let script = swash::text::Script::Latin; + let color = image::ColorRgba([255, 0, 0, 255]); + // [xy, wh, text, font, font_size] + // println!("rendering text {text} of size {w}/{h} at {x}/{y} with font {font} {font_size}, color {color:?}, alignment {halign:?}/{valign:?}, script {script:?}, direction {direction:?}"); + let text = graphics::WidgetEnum::Text(graphics::Text { + direction, + font_size: font_size as f32, + font_handle, + color, + halign, + valign, + script, + text: text.into(), + pos: graphics::Rect { + left: x as i32, + bottom: y as i32, + height: h as u32, + width: w as u32, + }, + }); + Value::from_serde(&text) + .map_err(|err| ErrorTrait::from_str("kbd/make-text", &err.to_string())) + }), + ); + engine.register(":round-corners", Value::new_symbol(":round-corners")); + engine.register_func( + "kbd/make-rect", + Box::new(|_vm, args| { + let mut args = args.into_iter(); + let Some(xy) = args.next() else { + return Err(ErrorTrait::invalid_argc("kbd/make-rect")); + }; + let Some(wh) = args.next() else { + return Err(ErrorTrait::invalid_argc("kbd/make-rect")); + }; + let mut round = 0.0f64; + while let Some(arg) = args.next() { + match arg { + Value::Symbol(x) if x == ":round-corners" => { + round = 1.0; + if let Some(arg) = args.next() { + let Value::Float(arg) = arg else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-rect", + "float", + &format!("{arg:?}"), + )); + }; + round = arg; + } + } + x => { + return Err(ErrorTrait::invalid_argt( + "kbd/make-rect", + "make-rect keyword arguments", + &format!("{x:?}"), + )) + } + } + } + let Value::Pair(xy) = xy else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-rect", + "pair", + &format!("{xy:?}"), + )); + }; + let Value::Pair(wh) = wh else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-rect", + "pair", + &format!("{wh:?}"), + )); + }; + let (x, y) = *xy; + let (w, h) = *wh; + let x1 = to_i64("kbd/make-rect", x.clone())?; + let y1 = to_i64("kbd/make-rect", y.clone())?; + let x_w = add_to_i64("kbd/make-rect", x, w)?; + let y_h = add_to_i64("kbd/make-rect", y, h)?; + let (x, y) = (x1, y1); + let (w, h) = (x_w - x, y_h - y); + let color = image::ColorRgba([0, 0, 0, 255]); + // [xy, wh, text, font, font_size] + println!( + "rendering rect of size {w}/{h} at {x}/{y} with rounding {round}, color {color:?}" + ); + let rect = graphics::WidgetEnum::Shape(graphics::SimpleShape { + col: color, + pos: graphics::Rect { + left: x as i32, + bottom: y as i32, + width: w as u32, + height: h as u32, + }, + round_corners_ratio: round as f32, + }); + Value::from_serde(&rect) + .map_err(|err| ErrorTrait::from_str("kbd/make-rect", &err.to_string())) + }), + ); + engine.register_func( + "kbd/make-layout", + Box::new(|_vm, args| { + let Ok::<[Value<_>; 1], _>([items]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/make-layout")); + }; + let Value::RevArray(items) = items else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-layout", + "list", + &format!("{items:?}"), + )); + }; + let mut layout = graphics::Layout::new(); + for item in items.into_iter().rev() { + let Value::RevArray(mut item) = item else { + return Err(ErrorTrait::invalid_argt( + "kbd/make-layout", + "list of lists", + &format!("list with {item:?}"), + )); + }; + let Some(Value::String(a)) = item.pop() else { + return Err(ErrorTrait::from_str( + "kbd/make-layout", + "each item's first element must be the item id", + )); + }; + let item = Value::RevArray(item) + .to_serde() + .map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))?; + // println!("{item:#?}"); + layout.add_child(item, &a, None); + } + Value::from_serde(&layout) + .map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string())) + }), + ); + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/width", + Box::new(move |_vm, args| { + let Ok::<[Value<_>; 0], _>([]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/width")); + }; + println!("getting keyboard width"); + Ok(Value::Int(ctx1.borrow().width.into())) + }), + ); + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/load-font", + Box::new(move |_vm, args| { + let Ok::<[Value<_>; 1], _>([path]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/load-font")); + }; + let Value::String(path) = path else { + return Err(ErrorTrait::invalid_argt( + "kbd/load-font", + "string", + &format!("{path:?}"), + )); + }; + println!("loading font {path:?}"); + (*ctx1) + .borrow_mut() + .font_db + .load_font(path) + .map(|()| Value::Null) + .map_err(|err| ErrorTrait::from_str("kbd/load-font", &err.to_string())) + }), + ); + let ctx1 = ctx.clone(); + engine.register_func( + "kbd/set-layout", + Box::new(move |_vm, args| { + let Ok::<[Value<_>; 1], _>([layout]) = args.try_into() else { + return Err(ErrorTrait::invalid_argc("kbd/set-layout")); + }; + let layout: graphics::Layout = layout + .to_serde() + .map_err(|err| ErrorTrait::from_str("kbd/set-layout", &err.to_string()))?; + (*ctx1).borrow_mut().layout = Some(layout); + Ok(Value::Null) + }), + ); + engine.gc(); + ScriptEnv(engine, ctx) +} + +fn assert_type(_: &T, _: &T) {} + +fn main() { + env_logger::init(); + let mut env = new_script_env(); + // engine.gc(); + let s = std::fs::read_to_string("tmp.scm").unwrap(); + let mut kbd = Keyboard::new(); + assert_type(&kbd, &(*env.1.borrow())); + env.eval(&mut kbd, &s).unwrap(); + kbd.env = Some(env); + + #[cfg(feature = "wayland")] + if std::env::var_os("WAYLAND_DISPLAY").is_some() { + return wayland::run(kbd); + } + #[cfg(feature = "framebuffer")] + framebuffer::run( + kbd, + framebuffer::FbSettings { + window_transparency: true, + always_redraw: true, + }, + ); } diff --git a/src/script.rs b/src/script.rs new file mode 100644 index 0000000..8a84045 --- /dev/null +++ b/src/script.rs @@ -0,0 +1,754 @@ +use std::{ + borrow::Cow, + fmt::Debug, + hash::{DefaultHasher, Hash, Hasher}, + mem, + rc::Rc, + time::Instant, +}; + +const HUMAN_SERDE: bool = false; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default)] +pub enum Value { + #[default] + Null, + Bool(bool), + Int(i64), + Float(f64), + String(String), + Symbol(Cow<'static, str>), + RevArray(Vec), + Pair(Box<(Self, Self)>), + Func(::Func), +} + +impl Clone for Value { + fn clone(&self) -> Self { + match self { + Self::Null => Self::Null, + Self::Bool(x) => Self::Bool(*x), + Self::Int(x) => Self::Int(*x), + Self::Float(x) => Self::Float(*x), + Self::String(x) => Self::String(x.clone()), + Self::Symbol(x) => Self::Symbol(x.clone()), + Self::RevArray(x) => Self::RevArray(x.clone()), + Self::Pair(x) => Self::Pair(x.clone()), + Self::Func(x) => Self::Func(x.clone()), + } + } +} + +impl Value { + pub fn new_symbol(s: impl Into>) -> Self { + Self::Symbol(s.into()) + } + pub fn from_serde(t: &T) -> Result { + t.serialize(crate::serde::Ser::::new(HUMAN_SERDE)) + } + pub fn to_serde<'de, T: Deserialize<'de>>(&self) -> Result { + T::deserialize(crate::serde::De::new(self.clone(), HUMAN_SERDE)) + } + pub fn add_ref(&mut self, engine: &mut E) { + if let Self::Func(x) = self { + x.add_ref(engine); + } + } + pub fn unref(&mut self, engine: &mut E) { + if let Self::Func(x) = self { + x.unref(engine); + } + } + pub fn new_pair(car: Self, mut cdr: Self) -> Self { + cdr.add_car(car); + cdr + } + pub fn car(&self) -> Option<&Self> { + match self { + Self::Pair(x) => Some(&x.0), + Self::RevArray(x) => x.last(), + _ => None, + } + } + pub fn split_off_car(&mut self) -> Option { + match self { + Self::Pair(_) => { + let mut tmp = Self::Null; + mem::swap(&mut tmp, self); + let Self::Pair(ab) = tmp else { unreachable!() }; + let (a, b) = *ab; + *self = b; + Some(a) + } + Self::RevArray(x) => x.pop(), + _ => None, + } + } + pub fn add_car(&mut self, car: Self) { + match self { + Self::RevArray(ref mut x) => { + x.push(car); + } + Self::Pair(x) if x.1.is_null() => { + let mut tmp = Self::Null; + mem::swap(&mut tmp, &mut x.0); + *self = Self::RevArray(vec![tmp, car]); + } + _ => { + let mut tmp = Self::Null; + mem::swap(&mut tmp, self); + *self = Self::Pair(Box::new((car, tmp))); + } + } + } + pub fn is_null(&self) -> bool { + matches!(self, Self::Null) || matches!(self, Self::RevArray(x) if x.is_empty()) + } +} +impl Eq for Value {} +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == std::cmp::Ordering::Equal + } +} +impl Ord for Value { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let cmp_key = |x: &Self| match x { + Self::Null => 0u8, + Self::Bool(_) => 1, + Self::Int(_) => 2, + Self::Float(_) => 3, + Self::String(_) => 4, + Self::Symbol(_) => 5, + Self::RevArray(_) => 6, + Self::Func(_) => 7, + Self::Pair(_) => 8, + }; + let cmp = cmp_key(self).cmp(&cmp_key(other)); + if !cmp.is_eq() { + return cmp; + } + match (self, other) { + (Self::Null, Self::Null) => cmp, + (Self::Bool(a), Self::Bool(b)) => a.cmp(b), + (Self::Int(a), Self::Int(b)) => a.cmp(b), + (Self::Float(a), Self::Float(b)) => a + .partial_cmp(b) + .unwrap_or_else(|| a.to_bits().cmp(&b.to_bits())), + (Self::String(a), Self::String(b)) => a.cmp(b), + (Self::Symbol(a), Self::Symbol(b)) => a.cmp(b), + (Self::RevArray(a), Self::RevArray(b)) => a.cmp(b), + (Self::Func(a), Self::Func(b)) => a.cmp(b), + (Self::Pair(a), Self::Pair(b)) => a.cmp(b), + _ => unreachable!(), + } + } +} +impl PartialOrd for Value { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Hash for Value { + fn hash(&self, state: &mut H) { + mem::discriminant(self).hash(state); + match self { + Self::RevArray(x) => x.hash(state), + Self::Null => {} + Self::Int(x) => x.hash(state), + Self::Bool(x) => x.hash(state), + Self::String(x) => x.hash(state), + Self::Symbol(x) => x.hash(state), + Self::Float(x) => x.to_bits().hash(state), + Self::Func(_) => {} + Self::Pair(x) => x.hash(state), + } + } +} + +pub trait ToVal { + type Engine: Engine; + fn to_val2(&self, _engine: &Self::Engine) -> Value { + self.to_val() + } + fn to_val(&self) -> Value { + unimplemented!() + } +} + +pub trait ErrorTrait { + fn from_str(name: &str, s: &str) -> Self; + fn invalid_argc(name: &str) -> Self; + fn invalid_argt(name: &str, exp: &str, found: &str) -> Self; +} + +pub trait Engine: Debug + Sized { + type Value: FromVal + ToVal; + type Error: Debug + ErrorTrait; + type Func: Clone + Debug + Ord + Func; + fn eval_t(&mut self, text: &str) -> Result, Self::Error>; + fn gc(&mut self); + fn register(&mut self, name: &str, val: Value); + // this doesnt have to be box, but it being a box saves some binary size + fn new_func( + &mut self, + name: &'static str, + func: Box< + dyn 'static + Fn(&mut Self, Vec>) -> Result, Self::Error>, + >, + ) -> Self::Func; + fn register_func( + &mut self, + name: &'static str, + func: Box< + dyn 'static + Fn(&mut Self, Vec>) -> Result, Self::Error>, + >, + ) { + let func = self.new_func(name, func); + self.register(name, Value::Func(func)); + } +} + +pub trait Func { + type Engine: Engine; + fn call( + &self, + engine: &mut Self::Engine, + args: Vec>, + ) -> Result, ::Error>; + fn add_ref(&mut self, engine: &mut Self::Engine); + fn unref(&mut self, engine: &mut Self::Engine); +} + +#[derive(Debug)] +pub enum MarwoodFunc { + Lambda(Rc, bool), + Closure(marwood::vm::heap::HeapRef, marwood::vm::heap::HeapRef, bool), + Builtin(Rc, bool), + Cont(Rc, bool), +} + +impl Clone for MarwoodFunc { + fn clone(&self) -> Self { + match self { + Self::Lambda(x, _) => Self::Lambda(x.clone(), false), + Self::Cont(x, _) => Self::Cont(x.clone(), false), + Self::Builtin(x, _) => Self::Builtin(x.clone(), false), + Self::Closure(a, b, _) => Self::Closure(*a, *b, false), + } + } +} + +impl Drop for MarwoodFunc { + fn drop(&mut self) { + let (x, b) = match self { + MarwoodFunc::Lambda(x, b) => (marwood::vm::vcell::VCell::Lambda(x.clone()), b), + MarwoodFunc::Cont(x, b) => (marwood::vm::vcell::VCell::Continuation(x.clone()), b), + MarwoodFunc::Builtin(x, b) => (marwood::vm::vcell::VCell::BuiltInProc(x.clone()), b), + MarwoodFunc::Closure(a, b, c) => (marwood::vm::vcell::VCell::Closure(*a, *b), c), + }; + if *b { + log::warn!("not unrefing {x} before drop"); + } + } +} + +impl Eq for MarwoodFunc {} + +impl PartialEq for MarwoodFunc { + fn eq(&self, other: &Self) -> bool { + self.cmp(other).is_eq() + } +} + +impl Ord for MarwoodFunc { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let key = |x: &Self| match x { + Self::Lambda(x, _) => &**x as *const _ as *const (), + Self::Builtin(x, _) => &**x as *const _ as *const (), + Self::Cont(x, _) => &**x as *const _ as *const (), + Self::Closure(a, b, _) => { + let mut hasher = DefaultHasher::new(); + hasher.write_usize(*a); + hasher.write_usize(*b); + hasher.finish() as usize as *const () + } + }; + key(self).cmp(&key(other)) + } +} + +impl PartialOrd for MarwoodFunc { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Func for MarwoodFunc { + type Engine = marwood::vm::Vm; + fn call( + &self, + engine: &mut Self::Engine, + args: Vec>, + ) -> Result, ::Error> { + use marwood::vm::{lambda::Lambda, opcode::OpCode, vcell::VCell}; + let (lambda, is_cont) = match self { + Self::Lambda(x, _) => (VCell::Lambda(x.clone()), false), + Self::Builtin(x, _) => (VCell::BuiltInProc(x.clone()), false), + Self::Cont(x, _) => (VCell::Continuation(x.clone()), true), + Self::Closure(a, b, _) => (VCell::Closure(*a, *b), false), + }; + let lambda = engine.heap.put(lambda); + let argc = args.len(); + let mut entry_lambda = Lambda::new(vec![]); + entry_lambda.set_top_level(); + for arg in args { + entry_lambda.emit(OpCode::MovImmediate); + entry_lambda.emit(VCell::from_val2(arg, engine)); + entry_lambda.emit(VCell::Acc); + entry_lambda.emit(OpCode::PushAcc); + } + entry_lambda.emit(OpCode::PushImmediate); + entry_lambda.emit(VCell::ArgumentCount(argc)); + entry_lambda.emit(OpCode::MovImmediate); + entry_lambda.emit(lambda); + entry_lambda.emit(VCell::Acc); + entry_lambda.emit(OpCode::CallAcc); + if !is_cont { + entry_lambda.emit(OpCode::Halt); + } + + let entry_lambda = engine.heap.put(entry_lambda); + + let ip = engine.ip; + engine.ip.0 = entry_lambda.as_ptr().unwrap(); + engine.ip.1 = 0; + + // continuations dont return, so return immediately + if is_cont { + return Ok(Value::Null); + } + + let ret = match engine.run_count_vcell(usize::MAX) { + Ok(x) => { + let ret = x.expect("tick count exceeded").clone().to_val2(engine); + Ok(ret) + } + Err(err) => Err(err), + }; + engine.ip = ip; + ret + } + fn add_ref(&mut self, engine: &mut Self::Engine) { + match self { + Self::Lambda(x, b) => { + engine.heap.mark_lambda(x, true); + *b = true; + } + Self::Closure(lambda, env, b) => { + engine.heap.mark(*lambda, true); + engine.heap.mark(*env, true); + *b = true; + } + Self::Cont(c, b) => { + engine.heap.mark_continuation(c, true); + *b = true; + } + Self::Builtin(_, b) => *b = true, + } + } + fn unref(&mut self, engine: &mut Self::Engine) { + match self { + Self::Lambda(x, b) => { + engine.heap.mark_lambda(x, false); + *b = false; + } + Self::Closure(lambda, env, b) => { + engine.heap.mark(*lambda, false); + engine.heap.mark(*env, false); + *b = false; + } + Self::Cont(c, b) => { + engine.heap.mark_continuation(c, false); + *b = false; + } + Self::Builtin(_, b) => *b = false, + } + } +} + +impl ErrorTrait for marwood::error::Error { + fn from_str(name: &str, s: &str) -> Self { + Self::Other(format!("{name}: {s}")) + } + fn invalid_argc(name: &str) -> Self { + Self::InvalidNumArgs(name.to_owned()) + } + fn invalid_argt(name: &str, exp: &str, found: &str) -> Self { + Self::InvalidArgs(name.to_owned(), exp.to_owned(), found.to_owned()) + } +} + +impl Engine for marwood::vm::Vm { + type Value = marwood::vm::vcell::VCell; + type Error = marwood::error::Error; + type Func = MarwoodFunc; + fn eval_t(&mut self, text: &str) -> Result, Self::Error> { + let mut rem = Some(text); + let mut last_len = 0; + let mut cells = Vec::new(); + loop { + let Some(rem1) = &rem else { + break; + }; + let (cell, remaining_text) = marwood::parse::parse_text(rem1)?; + cells.push(cell); + rem = remaining_text; + let Some(rem) = &rem else { + break; + }; + if last_len == rem.len() { + break; + } + last_len = rem.len(); + } + let cell = cells.pop().unwrap(); + for cell in &cells { + self.prepare_eval(cell)?; + // usize::MAX = max tick count + self.run_count_vcell(usize::MAX)? + .expect("tick count exceeded"); + self.stack.clear(); + self.run_gc(); + } + self.prepare_eval(&cell)?; + let ret = match self.run_count_vcell(usize::MAX) { + Ok(x) => Ok(x.expect("tick count exceeded").clone().to_val2(self)), + Err(x) => Err(x), + }; + self.stack.clear(); + ret + } + fn gc(&mut self) { + self.run_gc(); + } + fn register(&mut self, name: &str, val: Value) { + let symbol = self.heap.put(marwood::vm::vcell::VCell::symbol(name)); + let slot = self.globenv.get_binding(symbol.as_ptr().unwrap()); + let val = FromVal::from_val2(val, self); + self.globenv.put_slot(slot, val); + } + fn new_func( + &mut self, + name: &'static str, + func: Box< + dyn 'static + Fn(&mut Self, Vec>) -> Result, Self::Error>, + >, + ) -> Self::Func { + let func = marwood::vm::vcell::VCell::builtin(name, move |vm| { + // log::info!("calling {name}"); + let argc = vm.stack.pop()?.as_argc()?; + let mut args = vec![]; + for _ in 0..argc { + args.push(vm.heap.get(vm.stack.pop()?).to_val2(vm)); + } + args.reverse(); + // log::info!("calling! {name}"); + match func(vm, args) { + Ok(x) => Ok(FromVal::from_val2(x, vm)), + Err(err) => Err(err), + } + // log::info!("exiting {name}"); + }); + MarwoodFunc::Builtin( + match func { + marwood::vm::vcell::VCell::BuiltInProc(x) => x, + _ => unreachable!(), + }, + false, + ) + } +} + +pub trait FromVal: Sized { + type Engine: Engine; + fn from_val(_val: Value) -> Self { + unimplemented!() + } + fn from_val2(val: Value, _engine: &mut Self::Engine) -> Self { + Self::from_val(val) + } +} + +#[cfg(feature = "steel")] +impl FromVal for steel::SteelVal { + type Engine = steel::steel_vm::engine::Engine; + fn from_val(val: Value) -> Self { + match val { + Value::Float(x) => Self::NumV(x), + Value::String(x) => Self::StringV(x.into()), + Value::Array(x) => Self::ListV(x.into_iter().map(Self::from_val).collect()), + Value::Null => Self::ListV(Default::default()), + Value::Int(x) => { + let x = x; + if let Ok(val) = x.try_into() { + Self::IntV(val) + } else { + Self::BigNum(steel::gc::Gc::new(x.into())) + } + } + Value::Bool(x) => Self::BoolV(x), + Value::Map(x) => Self::ListV( + x.into_iter() + .map(|(k, v)| { + steel::values::lists::Pair::cons(Self::from_val(k), Self::from_val(v)) + }) + .map(|x| Self::Pair(steel::gc::Gc::new(x))) + .collect(), + ), + } + } +} + +impl FromVal for marwood::vm::vcell::VCell { + type Engine = marwood::vm::Vm; + fn from_val2(val: Value, engine: &mut Self::Engine) -> Self { + match val { + Value::Null => Self::Nil, + Value::Float(x) => Self::number(x), + Value::String(x) => Self::string(x), + Value::Symbol(x) => Self::symbol(x), + Value::RevArray(x) => x.into_iter().fold(Self::Nil, |cdr, car| { + let car = Self::from_val2(car, engine); + let car = if car.is_ptr() { + car.as_ptr().unwrap() + } else { + let r = engine.heap.alloc(); + *engine.heap.get_at_index_mut(r) = car; + r + }; + let cdr = if cdr.is_ptr() { + cdr.as_ptr().unwrap() + } else { + let dr = engine.heap.alloc(); + *engine.heap.get_at_index_mut(dr) = cdr; + dr + }; + engine.heap.put(Self::Pair(car, cdr)) + }), + Value::Pair(x) => { + let (car, cdr) = *x; + let car = Self::from_val2(car, engine); + let cdr = Self::from_val2(cdr, engine); + let car = if car.is_ptr() { + car.as_ptr().unwrap() + } else { + let r = engine.heap.alloc(); + *engine.heap.get_at_index_mut(r) = car; + r + }; + let cdr = if cdr.is_ptr() { + cdr.as_ptr().unwrap() + } else { + let dr = engine.heap.alloc(); + *engine.heap.get_at_index_mut(dr) = cdr; + dr + }; + engine.heap.put(Self::Pair(car, cdr)) + } + Value::Int(x) => Self::number(x), + Value::Bool(x) => Self::Bool(x), + Value::Func(x) => match &x { + MarwoodFunc::Lambda(x, _) => Self::Lambda(x.clone()), + MarwoodFunc::Cont(x, _) => Self::Continuation(x.clone()), + MarwoodFunc::Builtin(x, _) => Self::BuiltInProc(x.clone()), + MarwoodFunc::Closure(a, b, _) => Self::Closure(*a, *b), + }, + } + } +} + +impl ToVal for marwood::vm::vcell::VCell { + type Engine = marwood::vm::Vm; + fn to_val2(&self, engine: &Self::Engine) -> Value { + match self { + Self::Bool(val) => Value::Bool(*val), + Self::Char(val) => Value::String((*val).into()), + Self::Number(val) => match val { + marwood::number::Number::Float(x) => Value::Float(*x), + marwood::number::Number::Fixnum(x) => Value::Int(*x), + marwood::number::Number::BigInt(x) => { + if let Ok(val) = i64::try_from(&**x) { + Value::Int(val) + } else { + Value::String(val.to_string()) + } + } + marwood::number::Number::Rational(x) => { + let a = x.numer(); + let b = x.denom(); + Value::String(format!("{a}/{b}")) + } + }, + Self::Nil => Value::Null, + vcell @ Self::Pair(..) => { + let car = Self::to_val2(&vcell.as_car().unwrap(), engine); + let mut cdr = Self::to_val2(&vcell.as_cdr().unwrap(), engine); + cdr.add_car(car); + cdr + } + Self::Ptr(ptr) => Self::to_val2(engine.heap.get_at_index(*ptr), engine), + Self::String(s) => Value::String((*s.borrow()).clone()), + Self::Symbol(s) => Value::Symbol((**s).clone().into()), + Self::Undefined => Value::Null, + Self::Void => Value::Null, + Self::Closure(a, b) => Value::Func(MarwoodFunc::Closure(*a, *b, false)), + Self::Lambda(lambda) => Value::Func(MarwoodFunc::Lambda(lambda.clone(), false)), + Self::BuiltInProc(lambda) => Value::Func(MarwoodFunc::Builtin(lambda.clone(), false)), + Self::Continuation(lambda) => Value::Func(MarwoodFunc::Cont(lambda.clone(), false)), + Self::Macro(_) => Value::Null, + Self::Vector(vector) => { + let mut ret = Vec::with_capacity(vector.len()); + for idx in 0..vector.len() { + ret.push(Self::to_val2(&vector.get(idx).unwrap(), engine)); + } + ret.reverse(); + Value::RevArray(ret) + } + // Any internal values used by bytecode aren't convertible to Cells and + // result in a panic. + Self::Acc + | Self::ArgumentCount(_) + | Self::BasePointer(_) + | Self::BasePointerOffset(_) + | Self::EnvironmentPointer(_) + | Self::GlobalEnvSlot(_) + | Self::LexicalEnv(_) + | Self::LexicalEnvSlot(_) + | Self::LexicalEnvPtr(_, _) + | Self::OpCode(_) + | Self::InstructionPointer(_, _) => { + panic!("cannot convert VCell {} to Cell", self) + } + } + } +} + +/*impl From for Value { + fn from(value: toml_edit::DocumentMut) -> Self { + value.as_table().clone().into() + } +} +impl From for Value { + fn from(value: toml_edit::Table) -> Self { + value.into_inline_table().into() + } +} +impl From for Value { + fn from(value: toml_edit::InlineTable) -> Self { + Self::Map( + value + .into_iter() + .map(|(k, v)| (Self::String(k.to_string()), v.into())) + .collect(), + ) + } +} +impl From for Value { + fn from(value: toml_edit::Value) -> Self { + match value { + toml_edit::Value::Float(x) => Self::Float(x.into_value()), + toml_edit::Value::InlineTable(x) => x.into(), + toml_edit::Value::Datetime(x) => Self::String(x.to_string()), + toml_edit::Value::Boolean(x) => Self::Bool(x.into_value()), + toml_edit::Value::String(x) => Self::String(x.into_value()), + toml_edit::Value::Integer(x) => Self::Int(x.into_value()), + toml_edit::Value::Array(x) => Self::Array(x.into_iter().map(From::from).collect()), + } + } +} + +pub fn parse_json(x: &str) -> Result, serde_json5::Error> { + serde_json5::from_str(x) +} + +pub fn parse_toml(x: &str) -> Result, toml_edit::TomlError> { + toml_edit::DocumentMut::from_str(x).map(From::from) +}*/ + +#[derive(Debug)] +struct Sys(Instant); + +impl marwood::vm::SystemInterface for Sys { + fn time_utc(&self) -> u64 { + (Instant::now() - self.0).as_millis().try_into().unwrap() + } + fn terminal_dimensions(&self) -> (usize, usize) { + (80, 24) + } + fn display(&self, cell: &marwood::cell::Cell) { + print!("{cell}"); + } + fn write(&self, cell: &marwood::cell::Cell) { + print!("{cell}"); + } +} + +pub fn create_engine() -> impl Engine { + let mut vm = marwood::vm::Vm::new(); + vm.set_system_interface(Box::new(Sys(Instant::now()))); + vm +} + +#[cfg(test)] +mod test { + use crate::script::Func; + + use super::{Engine, Value}; + + #[test] + fn test() { + let mut vm = marwood::vm::Vm::new(); + vm.eval_text("(define (id x) x)").unwrap(); + let id_func = vm.eval_t("id").unwrap(); + let Value::Func(id_func) = id_func else { + panic!() + }; + assert_eq!(vm.eval_t("1").unwrap(), Value::Int(1)); + assert_eq!(vm.eval_t("1.0").unwrap(), Value::Float(1.0)); + assert_eq!(vm.eval_t("\"a\"").unwrap(), Value::String("a".to_owned())); + assert_eq!(vm.eval_t("'a").unwrap(), Value::new_symbol("a")); + assert_eq!(vm.eval_t("'a").unwrap(), Value::new_symbol("a")); + assert_eq!( + vm.eval_t("(cons 1 2)").unwrap(), + Value::new_pair(Value::Int(1), Value::Int(2)) + ); + assert_eq!( + vm.eval_t("(cons 1 '())").unwrap(), + Value::new_pair(Value::Int(1), Value::Null) + ); + assert_eq!( + vm.eval_t("(list 3 2 1)").unwrap(), + Value::RevArray(vec![Value::Int(1), Value::Int(2), Value::Int(3),]) + ); + #[allow(clippy::single_element_loop)] + for x in [ + Value::RevArray(vec![Value::Int(1), Value::Int(2), Value::Int(3)]), + /*Value::Map({ + let mut x = BTreeMap::new(); + x.insert(Value::Int(1), Value::Int(2)); + x.insert( + Value::Int(3), + Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)]), + ); + x + }),*/ + ] { + println!("{x:?}"); + let val = id_func.call(&mut vm, vec![x.clone()]).unwrap(); + assert_eq!(x, val); + } + } +} diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 0000000..82718b2 --- /dev/null +++ b/src/serde.rs @@ -0,0 +1,1195 @@ +// least boilerplatey rust code +use std::{borrow::Cow, marker::PhantomData}; + +use serde::{ + de::{EnumAccess, MapAccess, SeqAccess, VariantAccess}, + ser::{ + Error as _, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, + SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, + }, + Deserialize, Deserializer, +}; + +use crate::script::{Engine, Value}; + +pub mod script { + use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; + use swash::text::Script; + + pub fn serialize(x: &Script, ser: S) -> Result { + x.to_opentype().serialize(ser) + } + pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result { + let val = u32::deserialize(de)?; + Script::from_opentype(val).ok_or_else(|| D::Error::custom(format!("invalid script {val}"))) + } +} +pub mod direction { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use swash::shape::Direction; + + pub fn serialize(x: &Direction, ser: S) -> Result { + match x { + Direction::LeftToRight => false, + Direction::RightToLeft => true, + } + .serialize(ser) + } + pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result { + Ok(if bool::deserialize(de)? { + Direction::RightToLeft + } else { + Direction::LeftToRight + }) + } +} +struct Visitor(PhantomData); +impl<'de, Eng: Engine> serde::de::Visitor<'de> for Visitor { + type Value = Value; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("json value") + } + fn visit_i8(self, v: i8) -> Result + where + E: serde::de::Error, + { + Ok(Value::Int(v.into())) + } + fn visit_i16(self, v: i16) -> Result + where + E: serde::de::Error, + { + Ok(Value::Int(v.into())) + } + fn visit_i32(self, v: i32) -> Result + where + E: serde::de::Error, + { + Ok(Value::Int(v.into())) + } + fn visit_i64(self, v: i64) -> Result + where + E: serde::de::Error, + { + Ok(Value::Int(v)) + } + fn visit_u8(self, v: u8) -> Result + where + E: serde::de::Error, + { + Ok(Value::Int(v.into())) + } + fn visit_u16(self, v: u16) -> Result + where + E: serde::de::Error, + { + Ok(Value::Int(v.into())) + } + fn visit_u32(self, v: u32) -> Result + where + E: serde::de::Error, + { + Ok(Value::Int(v.into())) + } + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(Value::String(v.into())) + } + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: serde::de::Error, + { + Ok(Value::String(v.into())) + } + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + Ok(Value::String(v)) + } + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(Value::Null) + } + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(Value::Null) + } + fn visit_bool(self, v: bool) -> Result + where + E: serde::de::Error, + { + Ok(Value::Bool(v)) + } + fn visit_f32(self, v: f32) -> Result + where + E: serde::de::Error, + { + Ok(Value::Float(v.into())) + } + fn visit_f64(self, v: f64) -> Result + where + E: serde::de::Error, + { + Ok(Value::Float(v)) + } + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Value::deserialize(deserializer) + } + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Value::deserialize(deserializer) + } + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut w = Vec::new(); + while let Some(x) = seq.next_element::()? { + w.push(x); + } + w.reverse(); + Ok(Value::RevArray(w)) + } + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut w = Vec::new(); + while let Some((k, v)) = map.next_entry::()? { + w.push(Value::new_pair(k, v)); + } + w.reverse(); + Ok(Value::RevArray(w)) + } +} +impl<'de, E: Engine> serde::Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(Visitor::(PhantomData)) + } +} + +pub struct Ser(bool, PhantomData); + +pub type Error = serde::de::value::Error; + +impl Ser { + pub fn new(human: bool) -> Self { + Self(human, PhantomData) + } +} + +impl serde::ser::Serializer for Ser { + type Ok = Value; + type Error = Error; + type SerializeSeq = SerSeq; + type SerializeMap = SerMap; + type SerializeTuple = SerSeq; + type SerializeStruct = SerMap; + type SerializeTupleStruct = SerSeq; + type SerializeTupleVariant = SerSeq; + type SerializeStructVariant = SerMap; + fn serialize_i8(self, v: i8) -> Result { + Ok(Value::Int(v.into())) + } + fn serialize_i16(self, v: i16) -> Result { + Ok(Value::Int(v.into())) + } + fn serialize_i32(self, v: i32) -> Result { + Ok(Value::Int(v.into())) + } + fn serialize_i64(self, v: i64) -> Result { + Ok(Value::Int(v)) + } + fn serialize_u8(self, v: u8) -> Result { + Ok(Value::Int(v.into())) + } + fn serialize_u16(self, v: u16) -> Result { + Ok(Value::Int(v.into())) + } + fn serialize_u32(self, v: u32) -> Result { + Ok(Value::Int(v.into())) + } + fn serialize_u64(self, v: u64) -> Result { + Ok(Value::Int( + v.try_into() + .map_err(|err| Self::Error::custom(format!("{err}")))?, + )) + } + fn serialize_i128(self, v: i128) -> Result { + Ok(Value::Int( + v.try_into() + .map_err(|err| Self::Error::custom(format!("{err}")))?, + )) + } + fn serialize_u128(self, v: u128) -> Result { + Ok(Value::Int( + v.try_into() + .map_err(|err| Self::Error::custom(format!("{err}")))?, + )) + } + fn serialize_unit_struct(self, name: &'static str) -> Result { + if self.is_human_readable() { + Ok(Value::new_symbol(name)) + } else { + Ok(Value::Null) + } + } + fn serialize_f32(self, v: f32) -> Result { + Ok(Value::Float(v as f64)) + } + fn serialize_f64(self, v: f64) -> Result { + Ok(Value::Float(v)) + } + fn serialize_str(self, v: &str) -> Result { + Ok(Value::String(v.to_owned())) + } + fn serialize_bool(self, v: bool) -> Result { + Ok(Value::Bool(v)) + } + fn serialize_char(self, v: char) -> Result { + Ok(Value::String(v.to_string())) + } + fn serialize_seq(self, len: Option) -> Result { + Ok(SerSeq::new(len, self.is_human_readable())) + } + fn serialize_none(self) -> Result { + Ok(Value::Null) + } + fn serialize_map(self, len: Option) -> Result { + Ok(SerMap::new(len, self.is_human_readable())) + } + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + serde::Serialize, + { + value.serialize(self) + } + fn serialize_unit(self) -> Result { + Ok(Value::Null) + } + fn serialize_tuple(self, len: usize) -> Result { + Ok(SerSeq::new(Some(len), self.is_human_readable())) + } + fn serialize_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + Ok(if self.is_human_readable() { + SerMap::new_with(len + 1, true, [Value::new_symbol(name)]) + } else { + SerMap::new(Some(len), false) + }) + } + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + Ok(if self.is_human_readable() { + Value::new_pair(Value::new_symbol(name), Value::new_symbol(variant)) + } else { + Value::Int(variant_index.into()) + }) + } + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + Ok(if self.is_human_readable() { + SerSeq::new_with(len + 1, true, [Value::new_symbol(name)]) + } else { + SerSeq::new(Some(len), false) + }) + } + fn serialize_tuple_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(if self.is_human_readable() { + SerSeq::new_with( + len + 2, + self.is_human_readable(), + [Value::new_symbol(name), Value::new_symbol(variant)], + ) + } else { + SerSeq::new_with( + len + 1, + self.is_human_readable(), + [Value::Int(variant_index.into())], + ) + }) + } + fn serialize_struct_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(if self.is_human_readable() { + SerMap::new_with( + len + 2, + self.is_human_readable(), + [Value::new_symbol(name), Value::new_symbol(variant)], + ) + } else { + SerMap::new_with( + len + 1, + self.is_human_readable(), + [Value::Int(variant_index.into())], + ) + }) + } + fn serialize_newtype_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + serde::Serialize, + { + let human = self.is_human_readable(); + let mut val = value.serialize(self)?; + if human { + val.add_car(Value::new_symbol(variant)); + val.add_car(Value::new_symbol(name)); + } else { + val.add_car(Value::Int(variant_index.into())); + } + Ok(val) + } + fn serialize_newtype_struct( + self, + name: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + serde::Serialize, + { + let human = self.is_human_readable(); + let mut val = value.serialize(self)?; + if human { + val.add_car(Value::new_symbol(name)); + } + Ok(val) + } + fn serialize_bytes(self, _v: &[u8]) -> Result { + Err(Self::Error::custom("byte serialization not implemented")) + } + fn is_human_readable(&self) -> bool { + self.0 + } +} + +pub struct SerSeq(Vec>, bool); +impl SerSeq { + fn new(len: Option, human_readable: bool) -> Self { + Self( + len.map_or_else(Vec::new, Vec::with_capacity), + human_readable, + ) + } + fn new_with( + len: usize, + human_readable: bool, + vals: impl IntoIterator>, + ) -> Self { + let mut ret = Self::new(Some(len), human_readable); + ret.0.extend(vals); + ret + } + fn serialize_element(&mut self, value: &T) -> Result<(), Error> + where + T: ?Sized + serde::Serialize, + { + self.0.push(value.serialize(Ser::::new(self.1))?); + Ok(()) + } + fn end(mut self) -> Result, Error> { + Ok(match self.0.len() { + 0 => Value::Null, + 1 => Value::new_pair(self.0.into_iter().next().unwrap(), Value::Null), + _ => { + self.0.reverse(); + Value::RevArray(self.0) + } + }) + } +} +impl SerializeSeq for SerSeq { + type Ok = Value; + type Error = Error; + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + self.serialize_element(value) + } + fn end(self) -> Result { + self.end() + } +} + +impl SerializeTuple for SerSeq { + type Ok = Value; + type Error = Error; + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + self.serialize_element(value) + } + fn end(self) -> Result { + self.end() + } +} +impl SerializeTupleStruct for SerSeq { + type Ok = Value; + type Error = Error; + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + self.serialize_element(value) + } + fn end(self) -> Result { + self.end() + } +} +impl SerializeTupleVariant for SerSeq { + type Ok = Value; + type Error = Error; + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + self.serialize_element(value) + } + fn end(self) -> Result { + self.end() + } +} + +pub struct SerMap(Vec>, bool); +impl SerMap { + fn new(len: Option, human_readable: bool) -> Self { + Self( + len.map_or_else(Vec::new, Vec::with_capacity), + human_readable, + ) + } + fn new_with( + len: usize, + human_readable: bool, + vals: impl IntoIterator>, + ) -> Self { + let mut ret = Self::new(Some(len), human_readable); + ret.0.extend(vals); + ret + } + + fn serialize_key(&mut self, key: &T) -> Result<(), Error> + where + T: ?Sized + serde::Serialize, + { + self.0.push(key.serialize(Ser::::new(self.1))?); + Ok(()) + } + fn serialize_value(&mut self, value: &T) -> Result<(), Error> + where + T: ?Sized + serde::Serialize, + { + let car = self.0.pop().unwrap(); + let mut cdr = value.serialize(Ser::::new(self.1))?; + cdr.add_car(car); + self.0.push(cdr); + Ok(()) + } + fn serialize_entry(&mut self, key: &K, value: &V) -> Result<(), Error> + where + K: ?Sized + serde::Serialize, + V: ?Sized + serde::Serialize, + { + let car = key.serialize(Ser::::new(self.1))?; + let mut cdr = value.serialize(Ser::::new(self.1))?; + cdr.add_car(car); + self.0.push(cdr); + Ok(()) + } + fn end(mut self) -> Result, Error> { + Ok(match self.0.len() { + 0 => Value::Null, + 1 => Value::new_pair(self.0.into_iter().next().unwrap(), Value::Null), + _ => { + self.0.reverse(); + Value::RevArray(self.0) + } + }) + } + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Error> + where + T: ?Sized + serde::Serialize, + { + let mut cdr = value.serialize(Ser::::new(self.1))?; + cdr.add_car(Value::new_symbol(key)); + self.0.push(cdr); + Ok(()) + } +} + +impl SerializeMap for SerMap { + type Ok = Value; + type Error = Error; + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + self.serialize_key(key) + } + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + self.serialize_value(value) + } + fn serialize_entry(&mut self, key: &K, value: &V) -> Result<(), Self::Error> + where + K: ?Sized + serde::Serialize, + V: ?Sized + serde::Serialize, + { + self.serialize_entry(key, value) + } + fn end(self) -> Result { + self.end() + } +} + +impl SerializeStruct for SerMap { + type Ok = Value; + type Error = Error; + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + self.serialize_field(key, value) + } + fn end(self) -> Result { + self.end() + } +} + +impl SerializeStructVariant for SerMap { + type Ok = Value; + type Error = Error; + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + serde::Serialize, + { + self.serialize_field(key, value) + } + fn end(self) -> Result { + self.end() + } +} + +pub struct De(Value, bool); + +impl De { + pub fn new(v: Value, human: bool) -> Self { + Self(v, human) + } +} + +impl<'de, E: Engine> Deserializer<'de> for De { + type Error = Error; + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::String(x) => visitor.visit_string(x), + Value::Null => visitor.visit_none(), + Value::RevArray(x) if x.is_empty() => visitor.visit_none(), + Value::Pair(_) => visitor.visit_seq(self), + Value::Int(x) => visitor.visit_i64(x), + Value::Bool(x) => visitor.visit_bool(x), + Value::Func(_) => Err(Error::custom("func deser not supported")), + Value::RevArray(_) => visitor.visit_seq(self), + Value::Float(x) => visitor.visit_f64(x), + Value::Symbol(_) => panic!(), // Err(Error::custom("unexpected symbol")), // visitor.visit_str(&x), + } + } + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_none() + } + fn deserialize_identifier(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self.0 { + Value::Symbol(Cow::Owned(x)) => visitor.visit_string(x), + Value::Symbol(Cow::Borrowed(x)) => visitor.visit_str(x), + _ => self.deserialize_any(visitor), + } + } + fn deserialize_enum( + mut self, + name: &'static str, + v: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.is_human_readable() && matches!(self.0.car(), Some(Value::Symbol(s)) if s == name) { + self.0.split_off_car(); + } + let (rest, field) = match self.0.split_off_car() { + Some(field) => (Some(self), field), + None => (None, self.0), + }; + + let field = match field { + Value::Symbol(x) => x, + Value::Int(x) => usize::try_from(x) + .ok() + .and_then(|x| v.get(x).copied()) + .map(Cow::from) + .ok_or_else(|| Error::custom("invalid enum field"))?, + _ => return Err(Error::custom("unknown enum field")), + }; + + visitor.visit_enum(EnumAcc(field, rest)) + } + fn deserialize_struct( + mut self, + name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.is_human_readable() && matches!(self.0.car(), Some(Value::Symbol(s)) if s == name) { + self.0.split_off_car(); + } + self.deserialize_map(visitor) + } + fn deserialize_map(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.0.is_null() { + visitor.visit_map(serde::de::value::MapDeserializer::new(std::iter::empty::<( + (), + (), + )>())) + } else { + visitor.visit_map(self) + } + } + fn deserialize_tuple_struct( + mut self, + name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.is_human_readable() && matches!(self.0.car(), Some(Value::Symbol(s)) if s == name) { + self.0.split_off_car(); + } + self.deserialize_seq(visitor) + } + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } + fn deserialize_seq(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.0.is_null() { + visitor.visit_seq(serde::de::value::SeqDeserializer::new( + std::iter::empty::<()>(), + )) + } else { + self.deserialize_any(visitor) + } + } + fn deserialize_newtype_struct( + mut self, + name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.is_human_readable() && matches!(self.0.car(), Some(Value::Symbol(s)) if s == name) { + self.0.split_off_car(); + } + visitor.visit_newtype_struct(self) + } + fn deserialize_unit_struct( + mut self, + name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if matches!(&self.0, Value::Symbol(s) if s == name) { + return visitor.visit_unit(); + } + if self.is_human_readable() && matches!(self.0.car(), Some(Value::Symbol(s)) if s == name) { + self.0.split_off_car(); + } + self.deserialize_unit(visitor) + } + fn deserialize_unit(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.0.is_null() { + visitor.visit_unit() + } else { + self.deserialize_any(visitor) + } + } + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if self.0.is_null() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_bytes(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_string(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_str(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_char(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_f64(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_f32(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_u64(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_u32(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_u16(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_u8(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_i8(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn is_human_readable(&self) -> bool { + self.1 + } + fn deserialize_i16(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_i32(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_i64(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_bool(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_i128(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } + fn deserialize_u128(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_any(visitor) + } +} + +impl<'de, E: Engine> SeqAccess<'de> for De { + type Error = Error; + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: serde::de::DeserializeSeed<'de>, + { + self.0 + .split_off_car() + .map(|x| seed.deserialize(De::new(x, self.1))) + .transpose() + } + fn size_hint(&self) -> Option { + match &self.0 { + Value::Null => Some(0), + Value::Pair(x) if x.1.is_null() => Some(1), + Value::RevArray(x) => Some(x.len()), + _ => None, + } + } + fn next_element(&mut self) -> Result, Self::Error> + where + T: Deserialize<'de>, + { + self.0 + .split_off_car() + .map(|x| T::deserialize(De::new(x, self.1))) + .transpose() + } +} + +impl<'de, E: Engine> MapAccess<'de> for De { + type Error = Error; + fn size_hint(&self) -> Option { + match &self.0 { + Value::Null => Some(0), + Value::Pair(x) if x.1.is_null() => Some(1), + Value::RevArray(x) => Some(x.len()), + _ => None, + } + } + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: serde::de::DeserializeSeed<'de>, + { + let Some(mut v) = self.0.split_off_car() else { + return Ok(None); + }; + let Some(k) = v.split_off_car() else { + return Ok(None); + }; + let ret = seed.deserialize(De::new(k, self.1)); + self.0.add_car(v); + ret.map(Some) + } + fn next_key(&mut self) -> Result, Self::Error> + where + K: Deserialize<'de>, + { + let Some(mut v) = self.0.split_off_car() else { + return Ok(None); + }; + let Some(k) = v.split_off_car() else { + return Ok(None); + }; + let ret = K::deserialize(De::new(k, self.1)); + self.0.add_car(v); + ret.map(Some) + } + fn next_value_seed(&mut self, seed: V) -> Result + where + V: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(De::new(self.0.split_off_car().unwrap(), self.1)) + } + fn next_value(&mut self) -> Result + where + V: Deserialize<'de>, + { + V::deserialize(De::new(self.0.split_off_car().unwrap(), self.1)) + } + fn next_entry_seed( + &mut self, + kseed: K, + vseed: V, + ) -> Result, Self::Error> + where + K: serde::de::DeserializeSeed<'de>, + V: serde::de::DeserializeSeed<'de>, + { + let Some(mut v) = self.0.split_off_car() else { + return Ok(None); + }; + let Some(k) = v.split_off_car() else { + return Ok(None); + }; + let k = kseed.deserialize(De::new(k, self.1))?; + let v = vseed.deserialize(De::new(v, self.1))?; + Ok(Some((k, v))) + } + fn next_entry(&mut self) -> Result, Self::Error> + where + K: Deserialize<'de>, + V: Deserialize<'de>, + { + let Some(mut v) = self.0.split_off_car() else { + return Ok(None); + }; + let Some(k) = v.split_off_car() else { + return Ok(None); + }; + let k = K::deserialize(De::new(k, self.1))?; + let v = V::deserialize(De::new(v, self.1))?; + Ok(Some((k, v))) + } +} + +struct EnumAcc(Cow<'static, str>, Option>); +struct VarAcc(Option>); +impl<'de, E: Engine> EnumAccess<'de> for EnumAcc { + type Variant = VarAcc; + type Error = Error; + fn variant(self) -> Result<(V, Self::Variant), Self::Error> + where + V: Deserialize<'de>, + { + Ok(( + V::deserialize(serde::de::value::CowStrDeserializer::new(self.0))?, + VarAcc(self.1), + )) + } + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: serde::de::DeserializeSeed<'de>, + { + Ok(( + seed.deserialize(serde::de::value::CowStrDeserializer::new(self.0))?, + VarAcc(self.1), + )) + } +} + +impl<'de, E: Engine> VariantAccess<'de> for VarAcc { + type Error = Error; + fn unit_variant(self) -> Result<(), Self::Error> { + if self.0.is_none() { + Ok(()) + } else { + Err(Error::custom("not a unit variant")) + } + } + fn tuple_variant(self, len: usize, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.0 + .ok_or_else(|| Error::custom("unit variant"))? + .deserialize_tuple(len, visitor) + } + fn struct_variant( + self, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + self.0 + .ok_or_else(|| Error::custom("unit variant"))? + .deserialize_struct("", fields, visitor) + } + fn newtype_variant(self) -> Result + where + T: Deserialize<'de>, + { + T::deserialize(self.0.ok_or_else(|| Error::custom("unit variant"))?) + } + fn newtype_variant_seed(self, seed: T) -> Result + where + T: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(self.0.ok_or_else(|| Error::custom("unit variant"))?) + } +} + +#[cfg(test)] +mod test { + use std::{ + collections::HashMap, + fmt::Debug, + net::{IpAddr, Ipv4Addr}, + }; + + use serde::{Deserialize, Serialize}; + + use crate::script::Value; + + use super::{De, Ser}; + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + enum Test { + A, + B(u8, u8), + C { x: (u8, u8), y: (u8, u8) }, + D(), + E {}, + F(u8), + } + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test1; + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test2(); + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test3((u8, u8, u8)); + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test4(u8, u8); + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test5 { + x: (u8, u8), + y: (u8, u8, u8), + } + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test6 {} + + type Engine = marwood::vm::Vm; + type Val = Value; + fn roundtrip Deserialize<'a> + Serialize>(x: T) { + for human in [false, true] { + let ser: Val = x.serialize(Ser::new(human)).unwrap(); + println!("roundtrip {x:?} -> {ser:?}"); + let de = T::deserialize(De(ser.clone(), human)).unwrap(); + assert_eq!(x, de); + } + } + + #[test] + fn test() { + roundtrip(-0x5i8); + roundtrip(0x5u8); + roundtrip(-0x0500i16); + roundtrip(0x0500u16); + roundtrip(-0x0500_0000i32); + roundtrip(0x0500_0000u32); + roundtrip(-0x0500_0000_0000_0000i64); + roundtrip(0x0500_0000_0000_0000u64); + roundtrip(-0x0500_0000isize); + roundtrip(0x0500_0000usize); + roundtrip("a".to_owned()); + roundtrip(IpAddr::V4(Ipv4Addr::LOCALHOST)); + roundtrip(Some(IpAddr::V4(Ipv4Addr::LOCALHOST))); + roundtrip(None::); + roundtrip(()); + roundtrip((1, "test".to_owned(), (), (1), (5..=10, 6.0f32, 7.0f64))); + roundtrip::>(vec![]); + roundtrip(vec![1]); + roundtrip(Test::A); + roundtrip(Test::B(5, 6)); + roundtrip(Test::C { + x: (1, 2), + y: (3, 4), + }); + roundtrip(Test::D()); + roundtrip(Test::E {}); + roundtrip(Test::F(5)); + let mut v = HashMap::new(); + roundtrip(v.clone()); + v.insert(5, 6); + v.insert(6, 7); + roundtrip(v); + roundtrip(Test1); + roundtrip(Test2()); + roundtrip(Test3((5, 6, 7))); + roundtrip(Test4(5, 6)); + roundtrip(Test5 { + x: (1, 2), + y: (4, 5, 6), + }); + roundtrip(Test6 {}); + roundtrip(crate::image::ColorRgba::([1, 2, 3, 4])); + roundtrip(crate::graphics::Rect { + left: 0u8, + bottom: 1u8, + width: 2u8, + height: 3u8, + }); + } +} diff --git a/src/text.rs b/src/text.rs new file mode 100644 index 0000000..95dcbc0 --- /dev/null +++ b/src/text.rs @@ -0,0 +1,185 @@ +use std::{ + collections::HashMap, + hash::Hash, + io, + path::Path, + time::{Duration, Instant}, +}; + +use serde::{Deserialize, Serialize}; +use swash::{ + scale::{image::Image, Render, ScaleContext, Scaler, Source, StrikeWith}, + FontRef, +}; + +#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)] +pub struct CacheKey(pub(crate) u64); + +impl CacheKey { + /// Generates a new cache key. + pub fn new() -> Self { + use core::sync::atomic::{AtomicU64, Ordering}; + static KEY: AtomicU64 = AtomicU64::new(1); + Self(KEY.fetch_add(1, Ordering::Relaxed)) + } + + /// Returns the underlying value of the key. + pub fn value(self) -> u64 { + self.0 + } +} + +impl Default for CacheKey { + fn default() -> Self { + Self::new() + } +} + +#[derive(PartialEq)] +struct GlyphCacheKey { + font_key: CacheKey, + font_size: f32, + glyph_id: u16, + offset_x: f32, + offset_y: f32, +} + +impl Eq for GlyphCacheKey {} + +impl Hash for GlyphCacheKey { + fn hash(&self, state: &mut H) { + self.font_key.hash(state); + self.font_size.to_bits().hash(state); + self.glyph_id.hash(state); + self.offset_x.to_bits().hash(state); + self.offset_y.to_bits().hash(state); + } +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct FontHandle { + id: usize, + offset: u32, + key: CacheKey, +} + +#[derive(Default)] +pub struct FontDb { + font_files: Vec<&'static [u8]>, + fonts: HashMap, + glyph_cache: HashMap)>, +} + +impl FontDb { + pub fn load_font(&mut self, file: impl AsRef) -> io::Result<()> { + let mut any_added = false; + let data = std::fs::read(file)?; + let id = self.font_files.len(); + for font in std::iter::successors(Some(0usize), |i| Some(i + 1)) + .map_while(|i| FontRef::from_index(&data, i)) + { + let mut full = None; + for str in font.localized_strings() { + match str.id() { + swash::StringId::Full => { + if !str.is_decodable() { + continue; + } + let x: String = str.chars().collect(); + if !x.is_empty() { + full = Some(x); + break; + } + } + swash::StringId::Family => { + if !str.is_decodable() { + continue; + } + let x: String = str.chars().collect(); + if !x.is_empty() { + full = Some(x); + } + } + _ => {} + } + } + let Some(name) = full else { + continue; + }; + self.fonts.entry(name).or_insert_with(|| { + any_added = true; + FontHandle { + id, + offset: font.offset, + key: CacheKey::new(), + } + }); + } + if any_added { + self.font_files.push(Box::leak(data.into_boxed_slice())); + } + Ok(()) + } + pub fn get_font(&self, font_handle: FontHandle) -> Option> { + FontRef::from_offset(self.font_files[font_handle.id], font_handle.offset) + } + pub fn font_handle(&self, font_name: &str) -> Option { + self.fonts + .get(font_name) + .or_else(|| self.fonts.get(&(font_name.to_owned() + " Regular"))) + .copied() + } + pub fn _gc(&mut self) { + const GC_CUTOFF: Duration = Duration::from_secs(600); + let cutoff = Instant::now() - GC_CUTOFF; + self.glyph_cache.retain(|_k, v| v.0 >= cutoff); + } + pub fn render_glyph( + &mut self, + font_handle: FontHandle, + font_size: f32, + glyph: u16, + offset: (f32, f32), + scaler: Option<&mut Scaler>, + ) -> Option<&Image> { + let FontHandle { + id, + offset: font_offset, + key, + } = font_handle; + let ret = self + .glyph_cache + .entry(GlyphCacheKey { + font_key: key, + font_size, + glyph_id: glyph, + offset_x: offset.0, + offset_y: offset.1, + }) + .or_insert_with(|| { + let font_file = &self.font_files[id]; + let font = FontRef::from_offset(font_file, font_offset).unwrap(); + let mut render = Render::new(&[ + Source::ColorOutline(0), + Source::ColorBitmap(StrikeWith::BestFit), + Source::Bitmap(StrikeWith::BestFit), + Source::Outline, + ]); + let mut img = Image::new(); + render.offset((offset.0 % 1.0, -(offset.0 % 1.0)).into()); + ( + Instant::now(), + if let Some(scaler) = scaler { + render.render_into(scaler, glyph, &mut img) + } else { + let mut context = ScaleContext::new(); + let mut scaler = context.builder(font).size(font_size).hint(true).build(); + render.render_into(&mut scaler, glyph, &mut img) + } + .then_some(img), + ) + }); + ret.0 = Instant::now(); + ret.1.as_ref() + } +} diff --git a/src/wayland.rs b/src/wayland.rs index 6eb9328..9185e5a 100644 --- a/src/wayland.rs +++ b/src/wayland.rs @@ -1,15 +1,20 @@ use smithay_client_toolkit::{ - compositor::{CompositorHandler, CompositorState}, + compositor::{CompositorHandler, CompositorState, Region}, delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry, - delegate_seat, delegate_shm, + delegate_seat, delegate_shm, delegate_subcompositor, output::{OutputHandler, OutputState}, reexports::client::{ globals::registry_queue_init, - protocol::{wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, + protocol::{ + wl_output, wl_pointer, + wl_seat::{self, WlSeat}, + wl_shm, + wl_subsurface::WlSubsurface, + wl_surface::{self, WlSurface}, + }, Connection, QueueHandle, }, - registry::{ProvidesRegistryState, RegistryState}, - registry_handlers, + registry::{ProvidesRegistryState, RegistryHandler, RegistryState}, seat::{ pointer::{PointerEvent, PointerEventKind, PointerHandler}, Capability, SeatHandler, SeatState, @@ -21,46 +26,104 @@ use smithay_client_toolkit::{ WaylandSurface, }, shm::{slot::SlotPool, Shm, ShmHandler}, + subcompositor::SubcompositorState, }; -use crate::keyboard::Keyboard; +#[cfg(feature = "wayland-v1")] +use crate::{delegate_ime_context_v1, delegate_ime_v1}; +#[cfg(feature = "wayland-v2")] +use crate::{delegate_ime_manager_v2, delegate_ime_v2}; +use crate::{keyboard::Keyboard, script::Engine}; -pub fn run(keyboard: Keyboard<'static>) { - let conn = Connection::connect_to_env().unwrap(); +#[cfg(feature = "wayland-v1")] +mod ime_v1; +#[cfg(feature = "wayland-v2")] +mod ime_v2; + +#[cfg(feature = "wayland-v1")] +const FORCE_V1: bool = false; +#[cfg(feature = "wayland-v2")] +const FORCE_V2: bool = false; + +pub fn run(keyboard: Keyboard) { + let conn = Connection::connect_to_env().expect("failed to connect to the compositor"); 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 subcompositor = SubcompositorState::bind(compositor.wl_compositor().clone(), &globals, &qh) + .expect("wl_subcompositor is not available"); let surface = compositor.create_surface(&qh); + let subsurface = subcompositor.create_subsurface(surface.clone(), &qh); + subsurface.0.set_sync(); let layer = layer_shell.create_layer_surface(&qh, surface, Layer::Top, Some("simple_layer"), None); + let registry_state = RegistryState::new(&globals); + let seat_state = SeatState::new(&globals, &qh); + let seat = seat_state.seats().next().expect("no seats?"); + let ime_manager_v2 = ime_v2::ImeManagerV2::bind(&globals, &qh); + let ime_manager_v2 = if FORCE_V2 { + Some(ime_manager_v2.expect("zwp_input_method_v2 is not available")) + } else { + ime_manager_v2.ok() + }; + let ime = if let Some(ime_manager_v2) = ime_manager_v2 { + Ime::V2 { + ime: ime_v2::ImeV2::from(ime_manager_v2.wl_manager().get_input_method( + &seat, + &qh, + ime_v2::ImeV2Data::default(), + )), + } + } else { + /*let ime_v1 = ime_v1::ImeV1::bind(&globals, &qh); + let ime_v1 = if FORCE_V1 { + Some(ime_v1.expect("zwp_input_method_context_v1 is not available")) + } else { + ime_v1.ok() + }; + if let Some(ime_v1) = ime_v1 { + Ime::V1 { ime: ime_v1 } + } else {*/ + Ime::None + // } + }; + // let ime = Ime::None; + layer.set_anchor(Anchor::BOTTOM); let (w, h) = keyboard.size(); layer.set_size(w, h); layer.commit(); + let region = Region::new(&compositor).unwrap(); + subsurface.1.set_input_region(Some(region.wl_region())); + subsurface.1.commit(); let pool = SlotPool::new( usize::try_from(w).unwrap() * usize::try_from(h).unwrap() * 4, &shm, ) .expect("Failed to create pool"); + // zwp_input_method_context_v1::ZwpInputMethodContextV1::c 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), + seat_state, + registry_state, output_state: OutputState::new(&globals, &qh), shm, - exit: false, first_configure: true, pool, - width: 256, - height: 256, + popup: subsurface.1, + popup_sub: subsurface.0, + width: w, + height: h, shift: None, layer, pointer: None, keyboard, + ime, + seat, }; loop { event_queue.blocking_dispatch(&mut simple_layer).unwrap(); @@ -72,12 +135,23 @@ pub fn run(keyboard: Keyboard<'static>) { } } -struct KeyboardLayer { +enum Ime { + None, + #[cfg(feature = "wayland-v1")] + V1 { + ime: ime_v1::ImeV1, + }, + #[cfg(feature = "wayland-v2")] + V2 { + ime: ime_v2::ImeV2, + }, +} + +struct KeyboardLayer { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, shm: Shm, - exit: bool, first_configure: bool, pool: SlotPool, @@ -85,11 +159,15 @@ struct KeyboardLayer { height: u32, shift: Option, layer: LayerSurface, + popup: WlSurface, + popup_sub: WlSubsurface, pointer: Option, - keyboard: Keyboard<'static>, + keyboard: Keyboard, + seat: WlSeat, + ime: Ime, } -impl KeyboardLayer { +impl KeyboardLayer { pub fn draw(&mut self, qh: &QueueHandle) { let width = self.width; let height = self.height; @@ -106,20 +184,58 @@ impl KeyboardLayer { .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); - }); + let (damage, popup) = self.keyboard.draw( + canvas, + width, + height, + crate::image::PixelFormat::Argb8888Le, + 1, + false, + ); + if let Some(mut popup) = popup { + self.popup_sub.set_position(popup.left(), popup.top()); + let stride = popup.width() as i32 * 4; + let (buffer, canvas) = self + .pool + .create_buffer( + popup.width() as i32, + popup.height() as i32, + stride, + wl_shm::Format::Argb8888, + ) + .expect("create buffer"); + let damage = popup.draw( + canvas, + popup.width(), + popup.height(), + crate::image::PixelFormat::Argb8888Le, + 1, + true, + ); + self.popup.damage_buffer( + damage.left, + damage.top, + damage.width as i32, + damage.height as i32, + ); + buffer.attach_to(&self.popup).expect("buffer attach"); + } else { + let (buffer, _canvas) = self + .pool + .create_buffer(1, 1, 4, wl_shm::Format::Argb8888) + .expect("create buffer"); + self.popup.damage_buffer(0, 0, 1, 1); + buffer.attach_to(&self.popup).expect("buffer attach"); } + self.popup.commit(); // Damage the entire window - self.layer - .wl_surface() - .damage_buffer(0, 0, width as i32, height as i32); + self.layer.wl_surface().damage_buffer( + damage.left, + damage.top, + damage.width as i32, + damage.height as i32, + ); // Request our next frame self.layer @@ -138,7 +254,7 @@ impl KeyboardLayer { } } -impl CompositorHandler for KeyboardLayer { +impl CompositorHandler for KeyboardLayer { fn scale_factor_changed( &mut self, _conn: &Connection, @@ -186,7 +302,7 @@ impl CompositorHandler for KeyboardLayer { } } -impl OutputHandler for KeyboardLayer { +impl OutputHandler for KeyboardLayer { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } @@ -216,7 +332,7 @@ impl OutputHandler for KeyboardLayer { } } -impl LayerShellHandler for KeyboardLayer { +impl LayerShellHandler for KeyboardLayer { fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle, _layer: &LayerSurface) { self.exit = true; } @@ -245,7 +361,7 @@ impl LayerShellHandler for KeyboardLayer { } } -impl SeatHandler for KeyboardLayer { +impl SeatHandler for KeyboardLayer { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } @@ -285,7 +401,7 @@ impl SeatHandler for KeyboardLayer { fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } -impl PointerHandler for KeyboardLayer { +impl PointerHandler for KeyboardLayer { fn pointer_frame( &mut self, _conn: &Connection, @@ -301,9 +417,11 @@ impl PointerHandler for KeyboardLayer { match event.kind { PointerEventKind::Enter { .. } => { println!("Pointer entered @{:?}", event.position); + self.keyboard.show(); } PointerEventKind::Leave { .. } => { println!("Pointer left"); + self.keyboard.hide(); } PointerEventKind::Motion { .. } => {} PointerEventKind::Press { button, .. } => { @@ -325,26 +443,112 @@ impl PointerHandler for KeyboardLayer { } } -impl ShmHandler for KeyboardLayer { +#[cfg(feature = "wayland-v1")] +impl ime_v1::ImeContextV1Handler for KeyboardLayer { + fn reset(&mut self) {} + fn preferred_language(&mut self, _lang: &str) {} + fn content_type( + &mut self, + _hint: zwp_text_input_v1::ContentHint, + _purpose: zwp_text_input_v1::ContentPurpose, + ) { + } + fn commit_state(&mut self, _serial: u32) {} + fn invoke_action(&mut self, _button: u32, _index: u32) {} + fn set_surrounding_text(&mut self, _text: &str, _cursor: u32, _anchor: u32) {} +} + +#[cfg(feature = "wayland-v2")] +impl ime_v2::ImeV2Handler for KeyboardLayer { + fn apply(&mut self, changes: ime_v2::StateChanges) { + println!("{changes:?}"); + } + fn activate(&mut self) { + println!("activate"); + } + fn deactivate(&mut self) { + println!("deactivate"); + } +} + +#[cfg(feature = "wayland-v1")] +impl ime_v1::ImeV1Handler for KeyboardLayer { + fn activate(&mut self, _ctx: ime_v1::ImeContextV1) {} + fn deactivate(&mut self, _ctx: ime_v1::ImeContextV1) {} +} + +impl ShmHandler for KeyboardLayer { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } -delegate_compositor!(KeyboardLayer); -delegate_output!(KeyboardLayer); -delegate_shm!(KeyboardLayer); +delegate_compositor!(@ KeyboardLayer); +delegate_subcompositor!(@ KeyboardLayer); +delegate_output!(@ KeyboardLayer); +delegate_shm!(@ KeyboardLayer); -delegate_seat!(KeyboardLayer); -delegate_pointer!(KeyboardLayer); +delegate_seat!(@ KeyboardLayer); +delegate_pointer!(@ KeyboardLayer); -delegate_layer!(KeyboardLayer); +delegate_layer!(@ KeyboardLayer); -delegate_registry!(KeyboardLayer); +delegate_registry!(@ KeyboardLayer); -impl ProvidesRegistryState for KeyboardLayer { +#[cfg(feature = "wayland-v1")] +delegate_ime_v1!(@ KeyboardLayer); +#[cfg(feature = "wayland-v1")] +delegate_ime_context_v1!(@ KeyboardLayer); +#[cfg(feature = "wayland-v2")] +delegate_ime_v2!(@ KeyboardLayer); +#[cfg(feature = "wayland-v2")] +delegate_ime_manager_v2!(@ KeyboardLayer); + +impl ProvidesRegistryState for KeyboardLayer { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } - registry_handlers![OutputState, SeatState]; + fn runtime_add_global( + &mut self, + conn: &Connection, + qh: &QueueHandle, + name: u32, + interface: &str, + version: u32, + ) { + match interface { + "wl_output" => { + >::new_global( + self, conn, qh, name, interface, version, + ); + } + "wl_seat" => { + >::new_global( + self, conn, qh, name, interface, version, + ); + } + _ => {} + } + } + fn runtime_remove_global( + &mut self, + conn: &Connection, + qh: &QueueHandle, + name: u32, + interface: &str, + ) { + match interface { + "wl_output" => { + >::remove_global( + self, conn, qh, name, interface, + ); + } + "wl_seat" => { + >::remove_global( + self, conn, qh, name, interface, + ); + } + _ => {} + } + } } diff --git a/src/wayland/ime_v1.rs b/src/wayland/ime_v1.rs new file mode 100644 index 0000000..d52e0f6 --- /dev/null +++ b/src/wayland/ime_v1.rs @@ -0,0 +1,168 @@ +use smithay_client_toolkit::{ + error::GlobalError, + globals::{GlobalData, ProvidesBoundGlobal}, + reexports::{ + client::{ + globals::{BindError, GlobalList}, + Connection, Dispatch, QueueHandle, + }, + protocols::wp::{ + input_method::zv1::client::{ + zwp_input_method_context_v1::{self, ZwpInputMethodContextV1}, + zwp_input_method_v1::{self, ZwpInputMethodV1}, + }, + text_input::zv1::client::zwp_text_input_v1::{ContentHint, ContentPurpose}, + }, + }, +}; + +pub trait ImeContextV1Handler { + fn reset(&mut self); + fn preferred_language(&mut self, lang: &str); + fn content_type(&mut self, hint: ContentHint, purpose: ContentPurpose); + fn commit_state(&mut self, serial: u32); + /// button: e.g. BTN_LEFT / BTN_RIGHT + /// index = position in preedit + fn invoke_action(&mut self, button: u32, index: u32); + fn set_surrounding_text(&mut self, text: &str, cursor: u32, anchor: u32); +} + +pub trait ImeV1Handler { + fn activate(&mut self, ctx: ImeContextV1); + fn deactivate(&mut self, ctx: ImeContextV1); +} + +#[derive(Debug)] +pub struct ImeV1 { + wl_ime: ZwpInputMethodV1, +} + +#[derive(Debug)] +pub struct ImeContextV1 { + wl_ime_ctx: ZwpInputMethodContextV1, +} + +impl From for ImeContextV1 { + fn from(wl_ime: ZwpInputMethodContextV1) -> Self { + Self { wl_ime_ctx: wl_ime } + } +} + +impl ImeV1 { + pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result + where + State: Dispatch + ImeContextV1Handler + 'static, + { + let wl_ime = globals.bind(qh, 1..=1, GlobalData)?; + // Compositors must advertise Argb8888 and Xrgb8888, so let's reserve space for those formats. + Ok(Self { wl_ime }) + } + + #[allow(unused)] + pub fn wl_ime(&self) -> &ZwpInputMethodV1 { + &self.wl_ime + } +} + +impl ImeContextV1 { + #[allow(unused)] + pub fn wl_ime_ctx(&self) -> &ZwpInputMethodContextV1 { + &self.wl_ime_ctx + } +} + +impl ProvidesBoundGlobal for ImeV1 { + fn bound_global(&self) -> Result { + Ok(self.wl_ime.clone()) + } +} + +#[macro_export] +macro_rules! delegate_ime_v1 { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + smithay_client_toolkit::reexports::protocols::wp::input_method::zv1::client::zwp_input_method_v1::ZwpInputMethodV1: smithay_client_toolkit::globals::GlobalData + ] => $crate::wayland::ime_v1::ImeV1 + ); + }; +} +#[macro_export] +macro_rules! delegate_ime_context_v1 { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + smithay_client_toolkit::reexports::protocols::wp::input_method::zv1::client::zwp_input_method_context_v1::ZwpInputMethodContextV1: smithay_client_toolkit::globals::GlobalData// $crate::wayland::ime_v1::ImeContextV1Data + ] => $crate::wayland::ime_v1::ImeContextV1 + ); + }; +} + +impl Dispatch for ImeContextV1 +where + D: Dispatch + ImeContextV1Handler, +{ + fn event( + state: &mut D, + _proxy: &ZwpInputMethodContextV1, + event: zwp_input_method_context_v1::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + log::debug!(target: "zwp_input_method_context_v1", "received {event:?}"); + match event { + zwp_input_method_context_v1::Event::Reset => { + state.reset(); + } + zwp_input_method_context_v1::Event::ContentType { hint, purpose } => { + let Some(hint) = ContentHint::from_bits(hint) else { + log::debug!(target: "zwp_input_method_context_v1", "unknown hint: {hint:?}"); + return; + }; + let Ok(purpose) = ContentPurpose::try_from(purpose) else { + log::debug!(target: "zwp_input_method_context_v1", "unknown purpose: {purpose:?}"); + return; + }; + state.content_type(hint, purpose); + } + zwp_input_method_context_v1::Event::CommitState { serial } => { + state.commit_state(serial); + } + zwp_input_method_context_v1::Event::InvokeAction { button, index } => { + state.invoke_action(button, index); + } + zwp_input_method_context_v1::Event::SurroundingText { + text, + cursor, + anchor, + } => { + state.set_surrounding_text(&text, cursor, anchor); + } + zwp_input_method_context_v1::Event::PreferredLanguage { language } => { + state.preferred_language(&language); + } + _ => log::debug!(target: "zwp_input_method_context_v1", "unknown event"), + } + } +} + +impl Dispatch for ImeV1 +where + D: Dispatch + ImeV1Handler, +{ + fn event( + state: &mut D, + _proxy: &ZwpInputMethodV1, + event: zwp_input_method_v1::Event, + _data: &GlobalData, + _conn: &Connection, + _qh: &QueueHandle, + ) { + match event { + zwp_input_method_v1::Event::Activate { id } => state.activate(id.into()), + zwp_input_method_v1::Event::Deactivate { context } => state.deactivate(context.into()), + _ => log::debug!(target: "zwp_input_method_v1", "unknown event"), + } + } +} diff --git a/src/wayland/ime_v2.rs b/src/wayland/ime_v2.rs new file mode 100644 index 0000000..232aef1 --- /dev/null +++ b/src/wayland/ime_v2.rs @@ -0,0 +1,209 @@ +use std::sync::Mutex; + +use smithay_client_toolkit::{ + error::GlobalError, + globals::{GlobalData, ProvidesBoundGlobal}, + reexports::{ + client::{ + globals::{BindError, GlobalList}, + Connection, Dispatch, QueueHandle, WEnum, + }, + protocols::wp::text_input::zv3::client::zwp_text_input_v3::{ + ChangeCause, ContentHint, ContentPurpose, + }, + }, +}; +use wayland_protocols_misc::zwp_input_method_v2::client::{ + zwp_input_method_manager_v2::ZwpInputMethodManagerV2, + zwp_input_method_v2::{self, ZwpInputMethodV2}, +}; + +#[derive(Clone, Debug)] +pub struct SurroundingText { + /// surrounding text + pub text: String, + /// cursor byte offset + pub cursor: u32, + /// selection anchor byte offset, or same as cursor + pub anchor: u32, +} + +#[derive(Clone, Debug, Default)] +pub struct StateChanges { + pub surrounding_text: Option, + pub content_type: Option<(ContentHint, ContentPurpose)>, + pub change_cause: Option, +} + +pub trait ImeV2Handler { + /// Apply state changes + fn apply(&mut self, changes: StateChanges); + /// Focus on text input, activate IME + fn activate(&mut self); + /// Unfocus on text input, deactivate IME + fn deactivate(&mut self); + // TODO: Another IME started, kill ourselves + // fn unavailable(&mut self); +} + +#[derive(Debug)] +pub struct ImeV2 { + wl_ime: ZwpInputMethodV2, +} + +pub struct ImeManagerV2 { + wl_manager: ZwpInputMethodManagerV2, +} + +impl From for ImeV2 { + fn from(wl_ime: ZwpInputMethodV2) -> Self { + Self { wl_ime } + } +} + +impl From for ImeManagerV2 { + fn from(wl_manager: ZwpInputMethodManagerV2) -> Self { + Self { wl_manager } + } +} + +impl ImeManagerV2 { + pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result + where + State: Dispatch + 'static, + { + let wl_manager = globals.bind(qh, 1..=1, GlobalData)?; + // Compositors must advertise Argb8888 and Xrgb8888, so let's reserve space for those formats. + Ok(Self { wl_manager }) + } + + #[allow(unused)] + pub fn wl_manager(&self) -> &ZwpInputMethodManagerV2 { + &self.wl_manager + } +} + +impl ProvidesBoundGlobal for ImeV2 { + fn bound_global(&self) -> Result { + Ok(self.wl_ime.clone()) + } +} + +#[macro_export] +macro_rules! delegate_ime_v2 { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::wayland_protocols_misc::zwp_input_method_v2::client::zwp_input_method_v2::ZwpInputMethodV2: $crate::wayland::ime_v2::ImeV2Data + ] => $crate::wayland::ime_v2::ImeV2 + ); + }; +} + +#[macro_export] +macro_rules! delegate_ime_manager_v2 { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::wayland_protocols_misc::zwp_input_method_v2::client::zwp_input_method_manager_v2::ZwpInputMethodManagerV2: smithay_client_toolkit::globals::GlobalData + ] => $crate::wayland::ime_v2::ImeManagerV2 + ); + }; +} + +#[derive(Default)] +pub struct ImeV2Data { + surrounding_text: Mutex>, + change_cause: Mutex>>, + content_type: Mutex, WEnum)>>, +} + +impl Dispatch for ImeV2 +where + D: Dispatch + ImeV2Handler, +{ + fn event( + state: &mut D, + _proxy: &ZwpInputMethodV2, + event: zwp_input_method_v2::Event, + data: &ImeV2Data, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + log::debug!(target: "zwp_input_method_v2", "received {event:?}"); + match event { + zwp_input_method_v2::Event::ContentType { hint, purpose } => { + *data.content_type.lock().unwrap() = Some((hint, purpose)); + } + zwp_input_method_v2::Event::SurroundingText { + text, + cursor, + anchor, + } => { + *data.surrounding_text.lock().unwrap() = Some((text, cursor, anchor)); + } + zwp_input_method_v2::Event::Done => { + let mut changes = StateChanges::default(); + if let Some((text, cursor, anchor)) = + (*data.surrounding_text.lock().unwrap()).take() + { + changes.surrounding_text = Some(SurroundingText { + text, + cursor, + anchor, + }); + } + if let Some(cause) = (*data.change_cause.lock().unwrap()).take() { + match cause { + WEnum::Value(cause) => changes.change_cause = Some(cause), + WEnum::Unknown(cause) => { + log::debug!(target: "zwp_input_method_v2", "unknown cause: {cause:?}"); + } + }; + } + if let Some((hint, purpose)) = (*data.content_type.lock().unwrap()).take() { + match (hint, purpose) { + (WEnum::Value(hint), WEnum::Value(purpose)) => { + changes.content_type = Some((hint, purpose)); + } + (WEnum::Unknown(hint), WEnum::Unknown(purpose)) => { + log::debug!(target: "zwp_input_method_v2", "unknown hint: {hint:?}"); + log::debug!(target: "zwp_input_method_v2", "unknown purpose: {purpose:?}"); + } + (WEnum::Unknown(hint), _) => { + log::debug!(target: "zwp_input_method_v2", "unknown hint: {hint:?}"); + } + (_, WEnum::Unknown(purpose)) => { + log::debug!(target: "zwp_input_method_v2", "unknown purpose: {purpose:?}"); + } + } + } + state.apply(changes) + } + zwp_input_method_v2::Event::Activate => state.activate(), + zwp_input_method_v2::Event::Deactivate => state.deactivate(), + zwp_input_method_v2::Event::Unavailable => { + // todo!("IME became unavailable, perhaps another IME is getting in the way?"); + } + zwp_input_method_v2::Event::TextChangeCause { cause } => { + *data.change_cause.lock().unwrap() = Some(cause); + } + _ => log::debug!(target: "zwp_input_method_v2", "unknown event"), + } + } +} + +impl Dispatch for ImeManagerV2 +where + D: Dispatch, +{ + fn event( + _state: &mut D, + _proxy: &ZwpInputMethodManagerV2, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} diff --git a/tmp.scm b/tmp.scm new file mode 100644 index 0000000..5ea247f --- /dev/null +++ b/tmp.scm @@ -0,0 +1,91 @@ +(kbd/load-font "/home/user/.nix-profile/share/fonts/noto/NotoSans[wdth,wght].ttf") + +(kbd/defclass en () + ((init) (kbd/set-user-data 'state 'lower)) + ((state) (display (or (kbd/get-user-data 'state) 'lower)) (or (kbd/get-user-data 'state) 'lower)) + ((show-upper) (or (symbol=? (self state) 'upper) (symbol=? (self state) 'capslock))) + ((show-lower) (symbol=? (self state) 'lower)) + ((show-symbols) (symbol=? (self state) 'symbols)) + ((show-numbers) (symbol=? (self state) 'numbers)) + ((show-preferences) (symbol=? (self state) 'preferences)) + + ((rows) + (cond + ((self show-upper) '((Q W E R T Y U I O P) + (A S D F G H J K L) + (Shift_L Z X C V B N M BackSpace) + (show_numbers preferences space "." Return))) + ((self show-symbols) '((~ "`" "|" "·" √ π τ "÷" "×" "¶") + ("©" "®" "£" € "¥" ^ "°" * "{" "}") + (show_numbers_from_symbols \ / < > = "[" "]" BackSpace) + (show_letters preferences space "." Return))) + ((self show-numbers) '((1 2 3 4 5 6 7 8 9 0) + ("@" "#" $ % & - _ + "(" ")") + (show_symbols "," "\"" "'" : ";" ! ? BackSpace) + (show_letters preferences space "." Return))) + (else '((q w e r t y u i o p) + (a s d f g h j k l) + (Shift_L z x c v b n m BackSpace) + (show_numbers preferences space "." Return))))) + + ; 1 unit = keyboard width / total units in this row + ((button-width sym) + (cond + ((equal? sym 'Shift_L) 13.0) + ((equal? sym 'BackSpace) 13.0) + ((equal? sym 'show_numbers) 13.0) + ((equal? sym 'show_letters) 13.0) + ((equal? sym 'space) 54.0) + ((equal? sym 'Return) 13.0) + (else 10.0))) + + ((button-padding sym) + (cond + ((equal? sym 'a) (cons 5.0 0.0)) + ((equal? sym 'l) (cons 0.0 5.0)) + (else (cons 0.0 0.0)))) + + ((make-button sym x y w h) + (let ((sym (if (symbol? sym) (symbol->string sym) sym))) + (list sym (kbd/make-text (cons x y) (cons w h) sym "Noto Sans" 74)))) + + ((make-row syms y width height) + (let ((total-x (apply + (map (lambda (sym) + (let ((pad (self button-padding sym)) + (width (self button-width sym))) + (+ width (car pad) (cdr pad)))) + syms)))) + (define (scale u) (/ (* u width) total-x)) + (define (calc x syms) + (if (null? syms) + '() + (let* ((sym (car syms)) + (pad1 (self button-padding sym)) + (pad (cons (scale (car pad1)) (scale (cdr pad1)))) + (w (scale (self button-width sym)))) + (cons (self make-button sym (+ x (car pad)) y w height) + (calc (+ x (car pad) w (cdr pad)) (cdr syms)))))) + (calc 0 syms))) + + ((height) 500.0) + + ((make-layout rows kbd-width) + (define (height-list start end offset) + (if (> start end) '() (cons start (height-list (+ start offset) end offset)))) + (define (append-all lists) (apply append lists)) + (let* + ((height (self height)) + (row-height (/ height (length rows))) + (button-rows (map (lambda (row y) (self make-row row y kbd-width row-height)) + (reverse rows) + (height-list 0 height row-height)))) + (kbd/make-layout (cons (list "!bg" (kbd/make-rect (cons 0 0) (cons kbd-width height))) (append-all button-rows))))) + + ((kbd-layout) + (self make-layout (self rows) (kbd/width))) + + ; signals + + ((kbd/width-changed) + (display "a !!!") + (kbd/set-layout (self kbd-layout))))