From a6fe7645e9e196776cdcec08c294239903da83aa Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Tue, 8 Nov 2022 21:55:09 +1300 Subject: [PATCH] Tray icons --- Cargo.lock | 489 +++++++++++++++++- Cargo.toml | 2 +- rog-control-center/Cargo.toml | 11 +- .../data/rog-control-center.png | Bin 71839 -> 71953 bytes rog-control-center/src/app.rs | 26 +- rog-control-center/src/lib.rs | 3 +- rog-control-center/src/main.rs | 88 +++- rog-control-center/src/notify.rs | 7 +- 8 files changed, 590 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51da3381..b03103fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + [[package]] name = "arboard" version = "3.2.0" @@ -229,6 +235,30 @@ dependencies = [ "syn", ] +[[package]] +name = "atk" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +dependencies = [ + "atk-sys", + "bitflags", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "atomic-waker" version = "1.0.0" @@ -266,9 +296,9 @@ checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block" @@ -343,6 +373,30 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "cairo-rs" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "calloop" version = "0.10.1" @@ -368,6 +422,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-expr" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" +dependencies = [ + "smallvec", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -867,9 +930,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -927,6 +990,16 @@ dependencies = [ "instant", ] +[[package]] +name = "field-offset" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +dependencies = [ + "memoffset", + "rustc_version", +] + [[package]] name = "flate2" version = "1.0.24" @@ -1025,12 +1098,32 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.25" @@ -1078,6 +1171,65 @@ dependencies = [ "slab", ] +[[package]] +name = "gdk" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +dependencies = [ + "bitflags", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1121,6 +1273,36 @@ dependencies = [ "weezl", ] +[[package]] +name = "gio" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-io", + "gio-sys", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -1141,6 +1323,51 @@ dependencies = [ "serde", ] +[[package]] +name = "glib" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +dependencies = [ + "anyhow", + "heck 0.4.0", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "glow" version = "0.11.2" @@ -1218,6 +1445,72 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "gobject-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +dependencies = [ + "atk", + "bitflags", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "once_cell", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -1247,6 +1540,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1365,6 +1664,30 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libappindicator" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + [[package]] name = "libc" version = "0.2.137" @@ -1589,19 +1912,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "nix" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "memoffset", -] - [[package]] name = "nix" version = "0.24.2" @@ -1776,6 +2086,37 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "padlock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10569378a1dacd9f30dbe7ae49e054d2c45dc2f8ee49899903e09c3924e8b6f" + +[[package]] +name = "pango" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +dependencies = [ + "bitflags", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "parking" version = "2.0.0" @@ -1811,6 +2152,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pest" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8" +dependencies = [ + "thiserror", + "ucd-trie", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1888,6 +2239,30 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.47" @@ -2017,8 +2392,13 @@ dependencies = [ "dirs", "eframe", "egui", - "nix 0.20.2", + "env_logger", + "gtk", + "libappindicator", + "log", + "nix 0.25.0", "notify-rust", + "png_pong", "rog_anime", "rog_aura", "rog_dbus", @@ -2031,6 +2411,7 @@ dependencies = [ "tempfile", "tokio", "toml", + "tray-item", "zbus", ] @@ -2112,6 +2493,15 @@ dependencies = [ "libusb1-sys", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.11" @@ -2160,6 +2550,24 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.147" @@ -2377,7 +2785,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro2", "quote", "syn", @@ -2420,6 +2828,19 @@ dependencies = [ "numtoa", ] +[[package]] +name = "system-deps" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" +dependencies = [ + "cfg-expr", + "heck 0.4.0", + "pkg-config", + "toml", + "version-compare", +] + [[package]] name = "tauri-winrt-notification" version = "0.1.0" @@ -2608,6 +3029,24 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tray-item" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0914b62e00e8f51241806cb9f9c4ea6b10c75d94cae02c89278de6f4b98c7d0f" +dependencies = [ + "cocoa", + "core-graphics", + "gtk", + "libappindicator", + "libc", + "objc", + "objc-foundation", + "objc_id", + "padlock", + "winapi", +] + [[package]] name = "ttf-parser" version = "0.17.1" @@ -2620,6 +3059,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + [[package]] name = "udev" version = "0.6.3" @@ -2691,6 +3136,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version-compare" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 3ef9e302..89bc2afc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ serde_json = "^1.0" toml = "^0.5.9" log = "^0.4" -env_logger = "^0.9" +env_logger = "^0.9.3" glam = { version = "^0.22", features = ["serde"] } gumdrop = "^0.8" diff --git a/rog-control-center/Cargo.toml b/rog-control-center/Cargo.toml index 797fe9b3..8f06ce75 100644 --- a/rog-control-center/Cargo.toml +++ b/rog-control-center/Cargo.toml @@ -12,6 +12,10 @@ egui = { git = "https://github.com/flukejones/egui" } eframe= { git = "https://github.com/flukejones/egui" } #eframe= { git = "https://github.com/emilk/egui", default-features = false, features = ["dark-light", "default_fonts", "wgpu"] } +tray-item = "0.7.1" +libappindicator = "0.7" # Tray icon +gtk = "0.15" + daemon = { path = "../daemon" } rog_anime = { path = "../rog-anime" } rog_dbus = { path = "../rog-dbus" } @@ -21,6 +25,9 @@ rog_platform = { path = "../rog-platform" } supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", default-features = false } #supergfxctl = { path = "../../supergfxctl", default-features = false } +log.workspace = true +env_logger.workspace = true + tokio.workspace = true serde.workspace = true toml.workspace = true @@ -30,5 +37,7 @@ zbus.workspace = true dirs.workspace = true notify-rust.workspace = true -nix = "^0.20.0" +png_pong.workspace = true + +nix = "^0.25" tempfile = "3.2.0" \ No newline at end of file diff --git a/rog-control-center/data/rog-control-center.png b/rog-control-center/data/rog-control-center.png index 71e7b1761449bf8fca867c0b22d6445bd05f069f..958944bc036c36ecdc776d0962ff1b5abf40394e 100644 GIT binary patch delta 8530 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 MHtriJvoJ4j>B~;RhX4Qo delta 8726 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 AI{*Lx diff --git a/rog-control-center/src/app.rs b/rog-control-center/src/app.rs index 847364fe..51d71789 100644 --- a/rog-control-center/src/app.rs +++ b/rog-control-center/src/app.rs @@ -2,16 +2,18 @@ use std::{ f64::consts::PI, sync::{ atomic::{AtomicBool, AtomicU8, Ordering}, + mpsc::Receiver, Arc, }, - time::{Duration, Instant}, + time::{Duration, Instant}, io::Write, }; use egui::{Button, RichText}; use rog_platform::supported::SupportedFunctions; use crate::{ - config::Config, error::Result, page_states::PageDataStates, Page, RogDbusClientBlocking, + config::Config, error::Result, page_states::PageDataStates, tray::TrayToApp, Page, + RogDbusClientBlocking, get_ipc_file, SHOW_GUI, }; pub struct RogApp<'a> { @@ -29,6 +31,7 @@ pub struct RogApp<'a> { pub oscillator_freq: Arc, /// A toggle that toggles true/false when the oscillator reaches 0 pub oscillator_toggle: Arc, + pub app_cmd: Arc>, } impl<'a> RogApp<'a> { @@ -36,6 +39,7 @@ impl<'a> RogApp<'a> { pub fn new( config: Config, states: PageDataStates, + app_cmd: Arc>, _cc: &eframe::CreationContext<'_>, ) -> Result { let (dbus, _) = RogDbusClientBlocking::new()?; @@ -98,20 +102,38 @@ impl<'a> RogApp<'a> { oscillator3, oscillator_toggle, oscillator_freq, + app_cmd, }) } + + fn check_app_cmds(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) { + let Self { app_cmd, .. } = self; + + if let Ok(cmd) = app_cmd.try_recv() { + match cmd { + TrayToApp::Open => { + dbg!(); + get_ipc_file().unwrap().write_all(&[SHOW_GUI]).ok(); + }, + TrayToApp::Quit => _frame.close(), + } + } + } } impl<'a> eframe::App for RogApp<'a> { /// Called each time the UI needs repainting, which may be many times per second. /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + self.check_app_cmds(ctx, frame); + let Self { supported, asus_dbus: dbus, states, .. } = self; + states .refresh_if_notfied(supported, dbus) .map(|repaint| { diff --git a/rog-control-center/src/lib.rs b/rog-control-center/src/lib.rs index de4a5324..76c926db 100644 --- a/rog-control-center/src/lib.rs +++ b/rog-control-center/src/lib.rs @@ -18,6 +18,7 @@ pub mod page_states; pub mod pages; pub mod startup_error; pub mod widgets; +pub mod tray; #[cfg(feature = "mocking")] pub use mocking::RogDbusClientBlocking; @@ -96,7 +97,7 @@ pub fn get_ipc_file() -> Result { let tmp_dir = std::env::temp_dir().join("rog-gui"); let fifo_path = tmp_dir.join("ipc.pipe"); if let Err(e) = unistd::mkfifo(&fifo_path, stat::Mode::S_IRWXU) { - if !matches!(e, nix::Error::Sys(nix::errno::Errno::EEXIST)) { + if !matches!(e, nix::errno::Errno::EEXIST) { return Err(e)?; } } diff --git a/rog-control-center/src/main.rs b/rog-control-center/src/main.rs index 232a4e7c..514a4a6f 100644 --- a/rog-control-center/src/main.rs +++ b/rog-control-center/src/main.rs @@ -1,34 +1,48 @@ -use eframe::NativeOptions; +use eframe::{IconData, NativeOptions}; +use log::{error, LevelFilter}; use rog_aura::layouts::KeyLayout; +use rog_control_center::tray::{AppToTray, TrayToApp}; use rog_control_center::{ config::Config, error::Result, get_ipc_file, notify::start_notifications, on_tmp_dir_exists, - page_states::PageDataStates, print_versions, startup_error::AppErrorShow, RogApp, - RogDbusClientBlocking, SHOWING_GUI, SHOW_GUI, + page_states::PageDataStates, print_versions, startup_error::AppErrorShow, tray::init_tray, + RogApp, RogDbusClientBlocking, SHOWING_GUI, SHOW_GUI, }; use rog_platform::supported::SupportedFunctions; -use tokio::runtime::Runtime; - +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::Mutex; use std::{ fs::OpenOptions, io::{Read, Write}, path::PathBuf, sync::{atomic::AtomicBool, Arc}, }; +use tokio::runtime::Runtime; #[cfg(not(feature = "mocking"))] const DATA_DIR: &str = "/usr/share/rog-gui/"; #[cfg(feature = "mocking")] const DATA_DIR: &str = env!("CARGO_MANIFEST_DIR"); const BOARD_NAME: &str = "/sys/class/dmi/id/board_name"; +const APP_ICON_PATH: &str = "/usr/share/icons/hicolor/512x512/apps/rog-control-center.png"; fn main() -> Result<()> { print_versions(); + let mut logger = env_logger::Builder::new(); + logger + .target(env_logger::Target::Stdout) + .format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args())) + .filter(None, LevelFilter::Info) + .init(); // start tokio let rt = Runtime::new().expect("Unable to create Runtime"); // Enter the runtime so that `tokio::spawn` is available immediately. let _enter = rt.enter(); + let (send, recv) = channel(); + let update_tray = Arc::new(Mutex::new(send)); + let app_cmd = Arc::new(init_tray(recv)); + let native_options = eframe::NativeOptions { vsync: true, decorated: true, @@ -36,6 +50,7 @@ fn main() -> Result<()> { min_window_size: Some(egui::vec2(840.0, 600.0)), max_window_size: Some(egui::vec2(840.0, 600.0)), run_and_return: true, + icon_data: Some(load_icon()), ..Default::default() }; @@ -93,13 +108,18 @@ fn main() -> Result<()> { Err(_) => on_tmp_dir_exists().unwrap(), }; - let states = - setup_page_state_and_notifs(layout.clone(), &config, native_options.clone(), &dbus) - .unwrap(); + let states = setup_page_state_and_notifs( + layout.clone(), + &config, + native_options.clone(), + &dbus, + update_tray, + ) + .unwrap(); loop { if !start_closed { - start_app(states.clone(), native_options.clone())?; + start_app(states.clone(), native_options.clone(), app_cmd.clone())?; } let config = Config::load().unwrap(); @@ -130,6 +150,7 @@ fn setup_page_state_and_notifs( config: &Config, native_options: NativeOptions, dbus: &RogDbusClientBlocking, + update_tray: Arc>>, ) -> Result { // Cheap method to alert to notifications rather than spinning a thread for each // This is quite different when done in a retained mode app @@ -149,6 +170,7 @@ fn setup_page_state_and_notifs( profiles_notified.clone(), fans_notified.clone(), notifs_enabled.clone(), + update_tray, )?; let supported = match dbus.proxies().supported().supported_functions() { @@ -177,13 +199,57 @@ fn setup_page_state_and_notifs( ) } -fn start_app(states: PageDataStates, native_options: NativeOptions) -> Result<()> { +fn start_app( + states: PageDataStates, + native_options: NativeOptions, + app_cmd: Arc>, +) -> Result<()> { let mut ipc_file = get_ipc_file().unwrap(); ipc_file.write_all(&[SHOWING_GUI]).unwrap(); eframe::run_native( "ROG Control Center", native_options, - Box::new(move |cc| Box::new(RogApp::new(Config::load().unwrap(), states, cc).unwrap())), + Box::new(move |cc| { + Box::new(RogApp::new(Config::load().unwrap(), states, app_cmd, cc).unwrap()) + }), ); Ok(()) } + +/// Bah.. the icon dosn't work on wayland anyway, but we'll leave it in for now. +fn load_icon() -> IconData { + let path = PathBuf::from(APP_ICON_PATH); + let mut buf = Vec::new(); + let mut rgba = Vec::new(); + let mut height = 512; + let mut width = 512; + if path.exists() { + if let Ok(mut file) = OpenOptions::new() + .read(true) + .open(path) + .map_err(|e| error!("Error opening app icon: {e:?}")) + { + file.read_to_end(&mut buf) + .map_err(|e| error!("Error reading app icon: {e:?}")) + .ok(); + + let data = std::io::Cursor::new(buf); + let decoder = png_pong::Decoder::new(data).unwrap().into_steps(); + let png_pong::Step { raster, delay: _ } = decoder.last().unwrap().unwrap(); + + if let png_pong::PngRaster::Rgba8(ras) = raster { + rgba = ras.as_u8_slice().to_vec(); + width = ras.width(); + height = ras.height(); + } + } + } else { + error!("Missing {APP_ICON_PATH}") + } + + IconData { + height, + width, + rgba, + } +} diff --git a/rog-control-center/src/notify.rs b/rog-control-center/src/notify.rs index 0314901f..a6704d07 100644 --- a/rog-control-center/src/notify.rs +++ b/rog-control-center/src/notify.rs @@ -1,4 +1,4 @@ -use crate::error::Result; +use crate::{error::Result, tray::AppToTray}; use notify_rust::{Hint, Notification, NotificationHandle}; use rog_dbus::{ zbus_anime::AnimeProxy, zbus_led::LedProxy, zbus_platform::RogBiosProxy, @@ -10,6 +10,7 @@ use std::{ fmt::Display, sync::{ atomic::{AtomicBool, Ordering}, + mpsc::Sender, Arc, Mutex, }, }; @@ -73,6 +74,7 @@ pub fn start_notifications( profiles_notified: Arc, _fans_notified: Arc, notifs_enabled: Arc, + update_tray: Arc>>, ) -> Result<()> { let last_notification: SharedHandle = Arc::new(Mutex::new(None)); @@ -227,6 +229,9 @@ pub fn start_notifications( ), lock ); + if let Ok(lock) = update_tray.try_lock() { + lock.send(AppToTray::DgpuStatus(*status)).ok(); + } } } }