diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9cefea6..1b43aeec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: with: name: keyboard-configurator-mingw32-${{ github.sha }} - run: msiexec /i keyboard-configurator.msi /qb - - run: '& "C:\Program Files (x86)\System76\Keyboard Configurator\system76-keyboard-configurator.exe" --help-gtk' + - run: '& "C:\Program Files (x86)\System76\Keyboard Configurator\system76-keyboard-configurator.exe" --help' macos: runs-on: macos-latest diff --git a/Cargo.lock b/Cargo.lock index 5ad21993..6209aa5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,7 +62,7 @@ checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" dependencies = [ "atk-sys", "bitflags", - "glib 0.15.2", + "glib", "libc", ] @@ -72,10 +72,10 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" dependencies = [ - "glib-sys 0.15.1", - "gobject-sys 0.15.1", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.0.0", + "system-deps", ] [[package]] @@ -119,19 +119,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" -[[package]] -name = "cairo-rs" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b5725979db0c586d98abad2193cdb612dd40ef95cd26bd99851bf93b3cb482" -dependencies = [ - "bitflags", - "cairo-sys-rs 0.14.9", - "glib 0.14.8", - "libc", - "thiserror", -] - [[package]] name = "cairo-rs" version = "0.15.1" @@ -139,32 +126,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b869e97a87170f96762f9f178eae8c461147e722ba21dd8814105bf5716bf14a" dependencies = [ "bitflags", - "cairo-sys-rs 0.15.1", - "glib 0.15.2", + "cairo-sys-rs", + "glib", "libc", "thiserror", ] -[[package]] -name = "cairo-sys-rs" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b448b876970834fda82ba3aeaccadbd760206b75388fc5c1b02f1e343b697570" -dependencies = [ - "glib-sys 0.14.0", - "libc", - "system-deps 3.2.0", -] - [[package]] name = "cairo-sys-rs" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ - "glib-sys 0.15.1", + "glib-sys", "libc", - "system-deps 6.0.0", + "system-deps", ] [[package]] @@ -179,15 +155,6 @@ version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" -[[package]] -name = "cfg-expr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e" -dependencies = [ - "smallvec", -] - [[package]] name = "cfg-expr" version = "0.9.0" @@ -219,11 +186,41 @@ dependencies = [ "atty", "bitflags", "strsim 0.8.0", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] +[[package]] +name = "clap" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim 0.10.0", + "termcolor", + "textwrap 0.15.0", +] + +[[package]] +name = "clap_derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "concurrent-queue" version = "1.2.2" @@ -260,12 +257,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - [[package]] name = "enumflags2" version = "0.6.4" @@ -502,11 +493,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614258e81ec35ed8770e64a0838f3a47f95b398bc51e724d3b3fa09c1ee0f8d5" dependencies = [ "bitflags", - "cairo-rs 0.15.1", + "cairo-rs", "gdk-pixbuf", "gdk-sys", - "gio 0.15.2", - "glib 0.15.2", + "gio", + "glib", "libc", "pango", ] @@ -519,8 +510,8 @@ checksum = "172dfe1d9dfb62936bf7ad3ede2913a1b21b1e3db56990e46e00789201de9070" dependencies = [ "bitflags", "gdk-pixbuf-sys", - "gio 0.15.2", - "glib 0.15.2", + "gio", + "glib", "libc", ] @@ -530,11 +521,11 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413424d9818621fa3cfc8a3a915cdb89a7c3c507d56761b4ec83a9a98e587171" dependencies = [ - "gio-sys 0.15.1", - "glib-sys 0.15.1", - "gobject-sys 0.15.1", + "gio-sys", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.0.0", + "system-deps", ] [[package]] @@ -543,15 +534,15 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" dependencies = [ - "cairo-sys-rs 0.15.1", + "cairo-sys-rs", "gdk-pixbuf-sys", - "gio-sys 0.15.1", - "glib-sys 0.15.1", - "gobject-sys 0.15.1", + "gio-sys", + "glib-sys", + "gobject-sys", "libc", "pango-sys", "pkg-config", - "system-deps 6.0.0", + "system-deps", ] [[package]] @@ -576,23 +567,6 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] -[[package]] -name = "gio" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711c3632b3ebd095578a9c091418d10fed492da9443f58ebc8f45efbeb215cb0" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-io", - "gio-sys 0.14.0", - "glib 0.14.8", - "libc", - "once_cell", - "thiserror", -] - [[package]] name = "gio" version = "0.15.2" @@ -603,58 +577,26 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", - "gio-sys 0.15.1", - "glib 0.15.2", + "gio-sys", + "glib", "libc", "once_cell", "thiserror", ] -[[package]] -name = "gio-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a41df66e57fcc287c4bcf74fc26b884f31901ea9792ec75607289b456f48fa" -dependencies = [ - "glib-sys 0.14.0", - "gobject-sys 0.14.0", - "libc", - "system-deps 3.2.0", - "winapi", -] - [[package]] name = "gio-sys" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04b57719ccaacf2a0d9c79f151be629f3a3ef3991658ee2af0bb66287e4ea86c" dependencies = [ - "glib-sys 0.15.1", - "gobject-sys 0.15.1", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.0.0", + "system-deps", "winapi", ] -[[package]] -name = "glib" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c515f1e62bf151ef6635f528d05b02c11506de986e43b34a5c920ef0b3796a4" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "glib-macros 0.14.1", - "glib-sys 0.14.0", - "gobject-sys 0.14.0", - "libc", - "once_cell", - "smallvec", -] - [[package]] name = "glib" version = "0.15.2" @@ -666,30 +608,15 @@ dependencies = [ "futures-core", "futures-executor", "futures-task", - "glib-macros 0.15.1", - "glib-sys 0.15.1", - "gobject-sys 0.15.1", + "glib-macros", + "glib-sys", + "gobject-sys", "libc", "once_cell", "smallvec", "thiserror", ] -[[package]] -name = "glib-macros" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518" -dependencies = [ - "anyhow", - "heck 0.3.3", - "proc-macro-crate 1.1.0", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "glib-macros" version = "0.15.1" @@ -705,16 +632,6 @@ dependencies = [ "syn", ] -[[package]] -name = "glib-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1d60554a212445e2a858e42a0e48cece1bd57b311a19a9468f70376cf554ae" -dependencies = [ - "libc", - "system-deps 3.2.0", -] - [[package]] name = "glib-sys" version = "0.15.1" @@ -722,18 +639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c668102c6e15e0a7f6b99b59f602c2e806967bb86414f617b77e19b1de5b3fac" dependencies = [ "libc", - "system-deps 6.0.0", -] - -[[package]] -name = "gobject-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa92cae29759dae34ab5921d73fff5ad54b3d794ab842c117e36cafc7994c3f5" -dependencies = [ - "glib-sys 0.14.0", - "libc", - "system-deps 3.2.0", + "system-deps", ] [[package]] @@ -742,9 +648,9 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb1f0b3e4c08e2a0a490d1082ba9e902cdff8ff07091e85c6caec60d17e2ab" dependencies = [ - "glib-sys 0.15.1", + "glib-sys", "libc", - "system-deps 6.0.0", + "system-deps", ] [[package]] @@ -755,13 +661,13 @@ checksum = "22fa27087b0773dbf10d4d434fc38261f2df9a606571462fc13e61bea7bf2b8e" dependencies = [ "atk", "bitflags", - "cairo-rs 0.15.1", + "cairo-rs", "field-offset", "futures-channel", "gdk", "gdk-pixbuf", - "gio 0.15.2", - "glib 0.15.2", + "gio", + "glib", "gtk-sys", "gtk3-macros", "libc", @@ -777,15 +683,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "013419d486809ebafd7d8c2450afcfcf384c1a52dd079660f88c2a3b2e19f82f" dependencies = [ "atk-sys", - "cairo-sys-rs 0.15.1", + "cairo-sys-rs", "gdk-pixbuf-sys", "gdk-sys", - "gio-sys 0.15.1", - "glib-sys 0.15.1", - "gobject-sys 0.15.1", + "gio-sys", + "glib-sys", + "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.0", + "system-deps", ] [[package]] @@ -802,6 +708,12 @@ dependencies = [ "syn", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + [[package]] name = "heck" version = "0.3.3" @@ -912,6 +824,16 @@ dependencies = [ "syn", ] +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -941,15 +863,6 @@ dependencies = [ "unic-langid", ] -[[package]] -name = "itertools" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "0.4.8" @@ -1110,6 +1023,15 @@ dependencies = [ "serde", ] +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + [[package]] name = "palette" version = "0.5.0" @@ -1141,7 +1063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79211eff430c29cc38c69e0ab54bc78fa1568121ca9737707eee7f92a8417a94" dependencies = [ "bitflags", - "glib 0.15.2", + "glib", "libc", "once_cell", "pango-sys", @@ -1153,10 +1075,10 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7022c2fb88cd2d9d55e1a708a8c53a3ae8678234c4a54bf623400aeb7f31fac2" dependencies = [ - "glib-sys 0.15.1", - "gobject-sys 0.15.1", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.0.0", + "system-deps", ] [[package]] @@ -1166,8 +1088,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7876a45c1f1d1a75a2601dc6d9ef2cb5a8be0e3d76f909d82450759929035366" dependencies = [ "bitflags", - "cairo-rs 0.15.1", - "glib 0.15.2", + "cairo-rs", + "glib", "libc", "pango", "pangocairo-sys", @@ -1179,11 +1101,11 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cf746594916c81d5f739af9335c5f55a1f4606d80b3e1d821f18cf95a29494" dependencies = [ - "cairo-sys-rs 0.15.1", - "glib-sys 0.15.1", + "cairo-sys-rs", + "glib-sys", "libc", "pango-sys", - "system-deps 6.0.0", + "system-deps", ] [[package]] @@ -1639,18 +1561,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "strum" -version = "0.21.0" +name = "structopt" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" +dependencies = [ + "clap 2.33.3", + "lazy_static", + "structopt-derive", +] [[package]] -name = "strum_macros" -version = "0.21.1" +name = "structopt-derive" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" dependencies = [ "heck 0.3.3", + "proc-macro-error", "proc-macro2", "quote", "syn", @@ -1667,46 +1595,28 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "system-deps" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6" -dependencies = [ - "anyhow", - "cfg-expr 0.8.1", - "heck 0.3.3", - "itertools", - "pkg-config", - "strum", - "strum_macros", - "thiserror", - "toml", - "version-compare 0.0.11", -] - [[package]] name = "system-deps" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1487aaddaacbc5d60a2a507ba1617c5ca66c57dd0dd07d0c5efd5b693841d4" dependencies = [ - "cfg-expr 0.9.0", + "cfg-expr", "heck 0.3.3", "pkg-config", "toml", - "version-compare 0.1.0", + "version-compare", ] [[package]] name = "system76-keyboard-configurator" version = "1.1.0" dependencies = [ - "cairo-rs 0.14.9", "cascade", + "clap 3.1.5", "env_logger", "futures", - "gio 0.14.8", + "gio", "gtk", "i18n-embed", "i18n-embed-fl", @@ -1717,6 +1627,7 @@ dependencies = [ "rust-embed", "serde", "serde_json", + "structopt", "system76-keyboard-configurator-backend", "system76-keyboard-configurator-widgets", "winreg", @@ -1729,7 +1640,7 @@ dependencies = [ "cascade", "futures", "futures-timer", - "glib 0.15.2", + "glib", "hidapi", "i18n-embed", "i18n-embed-fl", @@ -1753,7 +1664,7 @@ version = "0.1.0" dependencies = [ "cascade", "futures", - "gio 0.15.2", + "gio", "gtk", "i18n-embed", "i18n-embed-fl", @@ -1773,7 +1684,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fa1f8f8e61d62f04c704268e040df5f37bd454af161c147bb2b491f68661ebe" dependencies = [ - "clap", + "clap 2.33.3", "downcast-rs", "hidapi", "libc", @@ -1797,6 +1708,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + [[package]] name = "thiserror" version = "1.0.30" @@ -1903,12 +1820,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "version-compare" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" - [[package]] name = "version-compare" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5f13be14..28156631 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "tools", "ffi", "backend", "widgets" ] [dependencies] cascade = "1" +clap = { version = "3", features = ["derive"] } futures = "0.3.13" gtk = { version = "0.15.0", features = ["v3_22"] } libc = "0.2" @@ -17,6 +18,7 @@ once_cell = "1.4" pangocairo = "0.15.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +structopt = "0.3" log = "0.4.0" env_logger = "0.8.3" backend = { package = "system76-keyboard-configurator-backend", path = "backend" } diff --git a/backend/src/backend.rs b/backend/src/backend.rs index f68f522f..fd8c2f64 100644 --- a/backend/src/backend.rs +++ b/backend/src/backend.rs @@ -99,10 +99,26 @@ impl Backend { Self::new_internal(DaemonClient::new_pkexec()) } - pub fn new() -> Result { + pub fn new_stdio() -> Result { Self::new_internal(DaemonServer::new_stdio()?) } + #[cfg(target_os = "linux")] + pub fn new() -> Result { + if unsafe { libc::geteuid() == 0 } { + info!("Already running as root"); + Self::new_stdio() + } else { + info!("Not running as root, spawning daemon with pkexec"); + Self::new_pkexec() + } + } + + #[cfg(not(target_os = "linux"))] + pub fn new() -> Result { + Self::new_stdio().expect("Failed to create server") + } + fn inner(&self) -> &BackendInner { BackendInner::from_instance(self) } diff --git a/backend/src/board.rs b/backend/src/board.rs index 18d9f632..0bf6d3c3 100644 --- a/backend/src/board.rs +++ b/backend/src/board.rs @@ -1,4 +1,4 @@ -use futures::{channel::mpsc as async_mpsc, prelude::*}; +use futures::{channel::mpsc as async_mpsc, prelude::*, stream::FuturesUnordered}; use glib::{ clone, prelude::*, @@ -9,13 +9,14 @@ use once_cell::sync::Lazy; use std::{ cell::{Cell, Ref, RefCell}, collections::HashMap, + pin::Pin, sync::Arc, }; use crate::daemon::ThreadClient; use crate::{ - Benchmark, BoardId, Daemon, DerefCell, Key, KeyMap, KeyMapLayer, Layer, Layout, Matrix, Nelson, - NelsonKind, + fl, Benchmark, BoardId, Daemon, DerefCell, Key, KeyMap, KeyMapLayer, Layer, Layout, Matrix, + Mode, Nelson, NelsonKind, }; #[derive(Default)] @@ -179,6 +180,16 @@ impl Board { &self.inner().model } + pub fn display_name(&self) -> String { + let name = &self.layout().meta.display_name; + let model = self.model().splitn(2, '/').nth(1).unwrap(); + if self.is_fake() { + format!("{} ({})", name, fl!("board-fake", model = model)) + } else { + format!("{} ({})", name, model) + } + } + pub fn version(&self) -> &str { &self.inner().version } @@ -254,6 +265,63 @@ impl Board { self.inner().matrix.borrow() } + pub async fn import_keymap(&self, keymap: KeyMap) { + // TODO: Ideally don't want this function to be O(Keys^2) + // TODO: Make sure it doesn't panic with invalid json with invalid indexes? + + let key_indices = self + .keys() + .iter() + .enumerate() + .map(|(i, k)| (&k.logical_name, i)) + .collect::>(); + + let futures = FuturesUnordered::>>>::new(); + + for (k, v) in &keymap.map { + for (layer, scancode_name) in v.iter().enumerate() { + let n = key_indices[&k]; + futures.push(Box::pin(async move { + if let Err(err) = self.keys()[n].set_scancode(layer, scancode_name).await { + error!("{}: {:?}", fl!("error-set-keymap"), err); + } + })); + } + } + + for (k, hs) in &keymap.key_leds { + let res = self.keys()[key_indices[&k]].set_color(*hs); + futures.push(Box::pin(async move { + if let Err(err) = res.await { + error!("{}: {}", fl!("error-key-led"), err); + } + })); + } + + for (i, keymap_layer) in keymap.layers.iter().enumerate() { + let layer = &self.layers()[i]; + if let Some((mode, speed)) = keymap_layer.mode { + futures.push(Box::pin(async move { + if let Err(err) = layer.set_mode(Mode::from_index(mode).unwrap(), speed).await { + error!("{}: {}", fl!("error-set-layer-mode"), err) + } + })); + } + futures.push(Box::pin(async move { + if let Err(err) = layer.set_brightness(keymap_layer.brightness).await { + error!("{}: {}", fl!("error-set-layer-brightness"), err) + } + })); + futures.push(Box::pin(async move { + if let Err(err) = layer.set_color(keymap_layer.color).await { + error!("{}: {}", fl!("error-set-layer-color"), err) + } + })); + } + + futures.collect::<()>().await; + } + pub fn export_keymap(&self) -> KeyMap { let mut map = HashMap::new(); let mut key_leds = HashMap::new(); diff --git a/backend/src/daemon/daemon_thread.rs b/backend/src/daemon/daemon_thread.rs index 4904493e..d623c9a2 100644 --- a/backend/src/daemon/daemon_thread.rs +++ b/backend/src/daemon/daemon_thread.rs @@ -442,11 +442,9 @@ impl Thread { } } - if have_new_board { - let _ = self - .response_channel - .unbounded_send(ThreadResponse::BoardLoadingDone); - } + let _ = self + .response_channel + .unbounded_send(ThreadResponse::BoardLoadingDone); Ok(()) } diff --git a/backend/src/daemon/server.rs b/backend/src/daemon/server.rs index 50f6e09d..5cd82c2c 100644 --- a/backend/src/daemon/server.rs +++ b/backend/src/daemon/server.rs @@ -50,7 +50,7 @@ impl DaemonServer { } }, Err(err) => { - error!("Failed to access LPC EC: {:?}", err); + info!("No LPC EC found: {:?}", err); } } diff --git a/backend/src/layout/meta.rs b/backend/src/layout/meta.rs index c62bd6fc..fef50eee 100644 --- a/backend/src/layout/meta.rs +++ b/backend/src/layout/meta.rs @@ -30,4 +30,6 @@ pub struct Meta { #[serde(default = "num_layers_default")] pub num_layers: u8, pub pressed_color: Rgb, + #[serde(default)] + pub is_usb: bool, } diff --git a/i18n/en/system76_keyboard_configurator.ftl b/i18n/en/system76_keyboard_configurator.ftl index a3581c8e..8c12514b 100644 --- a/i18n/en/system76_keyboard_configurator.ftl +++ b/i18n/en/system76_keyboard_configurator.ftl @@ -3,8 +3,6 @@ app-about = About {-name} app-title = System76 {-name} -board-fake = {$model}, fake - button-cancel = Cancel button-configure = Configure Keyboard button-disable = Disable @@ -16,15 +14,11 @@ button-stop = Stop error-disable-key = Failed to disable key error-export-keymap = Failed to export keymap error-import-keymap = Failed to import keymap -error-key-led = Failed to key LED error-open-file = Failed to open file error-save-leds = Failed to save LEDs error-set-keyboard-brightness = Error setting brightness error-set-keyboard-mode = Error setting keyboard mode error-set-keymap = Failed to set keymap -error-set-layer-brightness = Failed to set layer brightness -error-set-layer-color = Failed to set layer color -error-set-layer-mode = Failed to set layer mode error-unsupported-keymap = Unsupported keymap file error-unsupported-keymap-desc = Keymap file appears to be from newer Configurator version. diff --git a/i18n/en/system76_keyboard_configurator_backend.ftl b/i18n/en/system76_keyboard_configurator_backend.ftl index 1bf09f79..0581391c 100644 --- a/i18n/en/system76_keyboard_configurator_backend.ftl +++ b/i18n/en/system76_keyboard_configurator_backend.ftl @@ -1,3 +1,9 @@ +error-key-led = Failed to key LED +error-set-keymap = Failed to set keymap +error-set-layer-brightness = Failed to set layer brightness +error-set-layer-color = Failed to set layer color +error-set-layer-mode = Failed to set layer mode + mode-disabled = Disabled mode-solid-color = Per Layer Solid Color mode-per-key = Per Key Solid @@ -14,4 +20,6 @@ mode-raindrops = Elements mode-splash = Splashdown mode-multisplash = Meteor Shower -no-board = No board \ No newline at end of file +no-board = No board + +board-fake = {$model}, fake diff --git a/layouts/system76/launch_1/meta.json b/layouts/system76/launch_1/meta.json index 7a70c50c..d36814c7 100644 --- a/layouts/system76/launch_1/meta.json +++ b/layouts/system76/launch_1/meta.json @@ -1,5 +1,6 @@ { "display_name": "Launch Keyboard", + "is_usb": true, "has_mode": true, "has_per_layer": true, "num_layers": 4, diff --git a/layouts/system76/launch_alpha_1/meta.json b/layouts/system76/launch_alpha_1/meta.json index 689b78da..c204d9c3 100644 --- a/layouts/system76/launch_alpha_1/meta.json +++ b/layouts/system76/launch_alpha_1/meta.json @@ -1,7 +1,8 @@ { "display_name": "Launch Alpha Keyboard", + "is_usb": true, "has_brightness": true, "has_color": true, "pressed_color": "#202020", "keyboard": "system76/launch_alpha_1" -} \ No newline at end of file +} diff --git a/layouts/system76/launch_alpha_2/meta.json b/layouts/system76/launch_alpha_2/meta.json index 1817a72f..f7451e14 100644 --- a/layouts/system76/launch_alpha_2/meta.json +++ b/layouts/system76/launch_alpha_2/meta.json @@ -1,7 +1,8 @@ { "display_name": "Launch Alpha Keyboard", + "is_usb": true, "has_brightness": true, "has_color": true, "pressed_color": "#202020", "keyboard": "system76/launch_alpha_2" -} \ No newline at end of file +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..2b1eb1a2 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,130 @@ +use backend::{Backend, Board, KeyMap}; +use futures::channel::oneshot; +use gtk::{glib::clone, prelude::*}; +use std::{ + cell::RefCell, + fs::File, + io::{self, Write}, + process, + rc::Rc, +}; + +pub enum Device { + Any, + Internal, + Usb, +} + +impl Device { + pub fn new(internal: bool, usb: bool) -> Self { + if internal { + Self::Internal + } else if usb { + Self::Usb + } else { + Self::Any + } + } +} + +async fn backend_boards() -> (Backend, Vec) { + let backend = Backend::new().expect("Failed to create server"); + + let boards = Rc::new(RefCell::new(Vec::new())); + let id1 = backend.connect_board_added(clone!(@strong boards => move |board| { + boards.borrow_mut().push(board.clone()); + })); + + let (sender, receiver) = oneshot::channel::<()>(); + let sender = RefCell::new(Some(sender)); + let id2 = backend.connect_board_loading_done(move || { + if let Some(sender) = sender.borrow_mut().take() { + sender.send(()).unwrap(); + } + }); + backend.refresh(); + receiver.await.unwrap(); + + backend.disconnect(id1); + backend.disconnect(id2); + + (backend, boards.take()) +} + +pub async fn list_boards() { + let (_backend, boards) = backend_boards().await; + + for board in boards { + println!("{}", board.display_name()); + } +} + +fn match_board(board: &Board, device: &Device) -> bool { + let is_usb = board.layout().meta.is_usb; + match device { + Device::Any => true, + Device::Internal => !is_usb, + Device::Usb => is_usb, + } +} + +pub async fn board(device: Device) -> Board { + let (backend, mut boards) = backend_boards().await; + + boards = boards + .into_iter() + .filter(|board| match_board(board, &device)) + .collect(); + + if boards.is_empty() { + error!("No board detected"); + process::exit(1) + } else if boards.len() == 1 { + boards[0].clone() + } else { + eprintln!("Multiple boards detected"); + for (i, board) in boards.iter().enumerate() { + // Human readable name? + eprintln!("[{}] {}", i, board.display_name()); + } + print!("> "); + io::stdout().lock().flush().unwrap(); + let mut selection = String::new(); + io::stdin().read_line(&mut selection).unwrap(); + // XXX panic + boards + .get(selection.trim().parse::().unwrap()) + .unwrap() + .clone() + } +} + +// usb: bool +pub async fn save(device: Device, path: String) { + let board = board(device).await; + let keymap = board.export_keymap(); + match File::create(&path) { + Ok(file) => match keymap.to_writer_pretty(file) { + Ok(()) => (), + Err(err) => todo!(), + }, + Err(err) => todo!(), + } +} + +pub async fn load(device: Device, path: String) { + let board = board(device).await; + let keymap = match File::open(&path) { + Ok(file) => match KeyMap::from_reader(file) { + Ok(keymap) => keymap, + Err(err) => todo!(), + }, + Err(err) => todo!(), + }; + board.import_keymap(keymap).await; +} + +pub async fn reset(device: Device) { + let board = board(device).await; + board.import_keymap(board.layout().default.clone()).await; +} diff --git a/src/configurator_app.rs b/src/configurator_app.rs index bfabed31..c1a285e7 100644 --- a/src/configurator_app.rs +++ b/src/configurator_app.rs @@ -1,15 +1,71 @@ use cascade::cascade; -use gtk::{gdk, gio, glib, prelude::*, subclass::prelude::*}; -use std::cell::Cell; - -use crate::{about_dialog, fl, MainWindow, Page}; +use clap::Parser; +use gtk::{ + gdk, + gio::{self, subclass::ArgumentList}, + glib::{self, clone}, + prelude::*, + subclass::prelude::*, +}; +use std::{future::Future, pin::Pin}; + +use crate::{about_dialog, cli, fl, MainWindow, Page}; use backend::DerefCell; +#[derive(Debug, Parser)] +enum Verb { + #[clap(about = "Print list of detected keyboards")] + ListBoards, + #[clap(about = "Save layout to JSON")] + Save { + path: String, + #[clap(long)] + internal: bool, + #[clap(long, conflicts_with = "internal")] + usb: bool, + }, + #[clap(about = "Load layout from JSON")] + Load { + path: String, + #[clap(long)] + internal: bool, + #[clap(long, conflicts_with = "internal")] + usb: bool, + }, + #[clap(about = "Reset layout to default")] + Reset { + #[clap(long)] + internal: bool, + #[clap(long, conflicts_with = "internal")] + usb: bool, + }, +} + +#[derive(Debug, Parser)] +struct Opt { + #[clap(subcommand)] + verb: Option, + #[clap( + short = 'k', + long, + use_delimiter = true, + help_heading = "DEBUGGING OPTIONS", + help = "Keyboards to emulate, or 'all'" + )] + fake_keyboard: Vec, + #[clap(long, help_heading = "DEBUGGING OPTIONS", help = "Show debug layers")] + debug_layers: bool, + #[clap( + long, + help_heading = "DEBUGGING OPTIONS", + help = "Show Launch testing section" + )] + launch_test: bool, +} + #[derive(Default)] pub struct ConfiguratorAppInner { - phony_board_names: DerefCell>, - debug_layers: Cell, - launch_test: Cell, + opt: DerefCell, } #[glib::object_subclass] @@ -22,53 +78,63 @@ impl ObjectSubclass for ConfiguratorAppInner { impl ObjectImpl for ConfiguratorAppInner { fn constructed(&self, app: &ConfiguratorApp) { app.set_application_id(Some("com.system76.keyboardconfigurator")); - self.parent_constructed(app); - - app.add_main_option( - "fake-keyboard", - glib::Char::from(b'k'), - glib::OptionFlags::NONE, - glib::OptionArg::String, - "", - None, - ); - app.add_main_option( - "debug-layers", - glib::Char::from(b'\0'), - glib::OptionFlags::NONE, - glib::OptionArg::None, - "", - None, - ); - app.add_main_option( - "launch-test", - glib::Char::from(b'\0'), - glib::OptionFlags::NONE, - glib::OptionArg::None, - "", - None, - ); } } impl ApplicationImpl for ConfiguratorAppInner { - fn handle_local_options(&self, _app: &ConfiguratorApp, opts: &glib::VariantDict) -> i32 { - fn lookup(opts: &glib::VariantDict, key: &str) -> Option { - opts.lookup_value(key, None)?.get() + fn local_command_line(&self, app: &ConfiguratorApp, args: &mut ArgumentList) -> Option { + match Opt::try_parse_from(args.iter()) { + Ok(mut opt) => { + if opt.fake_keyboard == ["all".to_string()] { + opt.fake_keyboard = backend::layouts().iter().map(|s| s.to_string()).collect(); + } + + self.opt.set(opt); + + let fut: Pin>> = if let Some(verb) = &self.opt.verb { + match verb { + Verb::ListBoards => Box::pin(cli::list_boards()), + Verb::Save { + path, + internal, + usb, + } => Box::pin(cli::save(cli::Device::new(*internal, *usb), path.clone())), + Verb::Load { + path, + internal, + usb, + } => Box::pin(cli::load(cli::Device::new(*internal, *usb), path.clone())), + Verb::Reset { internal, usb } => { + Box::pin(cli::reset(cli::Device::new(*internal, *usb))) + } + } + } else { + app.register(None::<&gio::Cancellable>).unwrap(); + app.activate(); + return Some(0); + }; + + let main_loop = glib::MainLoop::new(None, false); + glib::MainContext::default().spawn_local(clone!(@strong main_loop => async move { + fut.await; + main_loop.quit(); + })); + main_loop.run(); + } + Err(err) => { + if err.kind() == clap::ErrorKind::DisplayHelp { + eprintln!("{}", err); + } else if err.kind() == clap::ErrorKind::DisplayVersion { + eprintln!("{}", err); + } else { + eprintln!("Error parsing arguments: {}", err); + return Some(1); + } + } } - let board_names = match lookup::(opts, "fake-keyboard").as_deref() { - Some("all") => backend::layouts().iter().map(|s| s.to_string()).collect(), - Some(value) => value.split(',').map(str::to_string).collect(), - None => vec![], - }; - - self.phony_board_names.set(board_names); - self.debug_layers.set(opts.contains("debug-layers")); - self.launch_test.set(opts.contains("launch-test")); - - -1 + Some(0) } fn startup(&self, app: &ConfiguratorApp) { @@ -117,15 +183,15 @@ impl ConfiguratorApp { } pub fn phony_board_names(&self) -> &[String] { - &self.inner().phony_board_names + &self.inner().opt.fake_keyboard } pub fn debug_layers(&self) -> bool { - self.inner().debug_layers.get() + self.inner().opt.debug_layers } pub fn launch_test(&self) -> bool { - self.inner().launch_test.get() + self.inner().opt.launch_test } } diff --git a/src/keyboard.rs b/src/keyboard.rs index 4166f22b..21ad6564 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -16,7 +16,7 @@ use std::{ }; use crate::{show_error_dialog, Backlight, KeyboardLayer, MainWindow, Page, Picker, Testing}; -use backend::{Board, DerefCell, KeyMap, Layout, Mode}; +use backend::{Board, DerefCell, KeyMap, Layout}; use widgets::SelectedKeys; #[derive(Default)] @@ -273,16 +273,6 @@ impl Keyboard { &self.inner().board } - pub fn display_name(&self) -> String { - let name = &self.layout().meta.display_name; - let model = self.board().model().splitn(2, '/').nth(1).unwrap(); - if self.board().is_fake() { - format!("{} ({})", name, fl!("board-fake", model = model)) - } else { - format!("{} ({})", name, model) - } - } - fn layout(&self) -> &Layout { &self.inner().board.layout() } @@ -336,67 +326,13 @@ impl Keyboard { } let _loader = self.toplevel().and_then(|x| { - Some( - x.downcast_ref::()? - .display_loader(&fl!("loading-keyboard", keyboard = self.display_name())), - ) + Some(x.downcast_ref::()?.display_loader(&fl!( + "loading-keyboard", + keyboard = self.board().display_name() + ))) }); - let key_indices = self - .board() - .keys() - .iter() - .enumerate() - .map(|(i, k)| (&k.logical_name, i)) - .collect::>(); - - let futures = FuturesUnordered::>>>::new(); - - for (k, v) in &keymap.map { - for (layer, scancode_name) in v.iter().enumerate() { - let n = key_indices[&k]; - futures.push(Box::pin(async move { - if let Err(err) = self.board().keys()[n] - .set_scancode(layer, scancode_name) - .await - { - error!("{}: {:?}", fl!("error-set-keymap"), err); - } - })); - } - } - - for (k, hs) in &keymap.key_leds { - let res = self.board().keys()[key_indices[&k]].set_color(*hs); - futures.push(Box::pin(async move { - if let Err(err) = res.await { - error!("{}: {}", fl!("error-key-led"), err); - } - })); - } - - for (i, keymap_layer) in keymap.layers.iter().enumerate() { - let layer = &self.board().layers()[i]; - if let Some((mode, speed)) = keymap_layer.mode { - futures.push(Box::pin(async move { - if let Err(err) = layer.set_mode(Mode::from_index(mode).unwrap(), speed).await { - error!("{}: {}", fl!("error-set-layer-mode"), err) - } - })); - } - futures.push(Box::pin(async move { - if let Err(err) = layer.set_brightness(keymap_layer.brightness).await { - error!("{}: {}", fl!("error-set-layer-brightness"), err) - } - })); - futures.push(Box::pin(async move { - if let Err(err) = layer.set_color(keymap_layer.color).await { - error!("{}: {}", fl!("error-set-layer-color"), err) - } - })); - } - - futures.collect::<()>().await; + self.board().import_keymap(keymap).await; } fn import(&self) { diff --git a/src/main.rs b/src/main.rs index 205cec00..14f4569d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use std::process; mod about_dialog; mod backlight; +mod cli; mod configurator_app; mod error_dialog; mod keyboard; diff --git a/src/main_window.rs b/src/main_window.rs index 18363e8e..24beb160 100644 --- a/src/main_window.rs +++ b/src/main_window.rs @@ -211,7 +211,7 @@ impl MainWindow { app.add_window(&window); let backend = cascade! { - daemon(); + Backend::new().expect("Failed to create server"); ..connect_board_loading(clone!(@weak window => move || { let loader = window.display_loader(&fl!("loading")); *window.inner().board_loading.borrow_mut() = Some(loader); @@ -305,7 +305,7 @@ impl MainWindow { ..insert(pango::AttrInt::new_weight(pango::Weight::Bold)); }; let label = cascade! { - gtk::Label::new(Some(&keyboard.display_name())); + gtk::Label::new(Some(&keyboard.board().display_name())); ..set_attributes(Some(&attr_list)); }; let window = self; @@ -398,20 +398,3 @@ impl MainWindow { Loader(self.clone(), load_hbox) } } - -#[cfg(target_os = "linux")] -fn daemon() -> Backend { - if unsafe { libc::geteuid() == 0 } { - info!("Already running as root"); - Backend::new() - } else { - info!("Not running as root, spawning daemon with pkexec"); - Backend::new_pkexec() - } - .expect("Failed to create server") -} - -#[cfg(not(target_os = "linux"))] -fn daemon() -> Backend { - Backend::new().expect("Failed to create server") -}