diff options
-rw-r--r-- | Cargo.lock | 241 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | po/Furtherance.pot | 2 | ||||
-rw-r--r-- | po/de.po | 4 | ||||
-rw-r--r-- | po/fi.po | 4 | ||||
-rw-r--r-- | po/fr.po | 4 | ||||
-rw-r--r-- | po/he.po | 2 | ||||
-rw-r--r-- | po/it.po | 4 | ||||
-rw-r--r-- | po/nb.po | 2 | ||||
-rw-r--r-- | po/nl.po | 4 | ||||
-rw-r--r-- | po/pt.po | 4 | ||||
-rw-r--r-- | po/ru.po | 4 | ||||
-rw-r--r-- | po/sk.po | 4 | ||||
-rw-r--r-- | po/tr.po | 4 | ||||
-rw-r--r-- | src/gtk/preferences_dialog.ui | 2 | ||||
-rw-r--r-- | src/helpers/idle.rs | 120 | ||||
-rw-r--r-- | src/helpers/wayland_idle.rs | 348 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/ui/window.rs | 12 |
19 files changed, 739 insertions, 39 deletions
diff --git a/Cargo.lock b/Cargo.lock index d5d4564..483bfd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ahash" @@ -248,6 +248,21 @@ dependencies = [ ] [[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -260,6 +275,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] name = "fallible-iterator" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -296,6 +321,7 @@ dependencies = [ "gtk4", "gtk4-macros", "itertools", + "lazy_static", "libadwaita", "log", "num-derive", @@ -303,6 +329,10 @@ dependencies = [ "once_cell", "rusqlite", "serde", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "x11rb", ] [[package]] @@ -426,6 +456,16 @@ dependencies = [ ] [[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] name = "getrandom" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -744,9 +784,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libadwaita" @@ -782,9 +822,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.133" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libdbus-sys" @@ -796,6 +836,16 @@ dependencies = [ ] [[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] name = "libsqlite3-sys" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -806,6 +856,12 @@ dependencies = [ ] [[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] name = "locale_config" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -993,6 +1049,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45c49fc4f91f35bae654f85ebb3a44d60ac64f11b3166ffa609def390c732d8" [[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + +[[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1062,6 +1127,19 @@ dependencies = [ ] [[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1077,6 +1155,12 @@ dependencies = [ ] [[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1373,6 +1457,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] +name = "wayland-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +dependencies = [ + "bitflags 2.4.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +dependencies = [ + "bitflags 2.4.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" +dependencies = [ + "bitflags 2.4.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1438,6 +1595,21 @@ dependencies = [ [[package]] name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" @@ -1460,6 +1632,12 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" @@ -1472,6 +1650,12 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" @@ -1484,6 +1668,12 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" @@ -1502,6 +1692,12 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" @@ -1514,6 +1710,12 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" @@ -1526,6 +1728,12 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" @@ -1538,6 +1746,12 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" @@ -1552,6 +1766,23 @@ dependencies = [ ] [[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index 94a010b..7f6b0a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,17 @@ directories = "5.0" gettext-rs = { version = "0.7", features = ["gettext-system"] } gtk4-macros = "0.8.2" itertools = "0.10.5" +lazy_static = "1.5.0" log = "0.4" num-derive = "0.3.3" num-traits = "0.2.15" once_cell = "1.17.1" rusqlite = { version = "0.28.0", features = ["backup"] } serde = { version = "1.0", features = ["derive"] } +wayland-client = "0.31.7" +wayland-protocols = { version = "0.32.5", features = ["client", "staging"] } +wayland-protocols-plasma = { version = "0.3.5", features = ["client"] } +x11rb = "0.13.1" [dependencies.gtk] package = "gtk4" @@ -30,4 +35,4 @@ features = ["gnome_46"] [dependencies.adw] package = "libadwaita" version = "0.6.0" -features = ["v1_5"] \ No newline at end of file +features = ["v1_5"] diff --git a/po/Furtherance.pot b/po/Furtherance.pot index 439d145..6fca2aa 100644 --- a/po/Furtherance.pot +++ b/po/Furtherance.pot @@ -100,7 +100,7 @@ msgid "Notify of idle" msgstr "" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" +msgid "(Wayland only)" msgstr "" #: src/gtk/preferences_window.ui:38 diff --git a/po/de.po b/po/de.po index cf4c94d..66d9812 100644 --- a/po/de.po +++ b/po/de.po @@ -272,8 +272,8 @@ msgid "Notify of idle" msgstr "Inaktivität melden" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(Nur GNOME)" +msgid "(Wayland only)" +msgstr "(Nur Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/po/fi.po b/po/fi.po index a6523f4..a8da004 100644 --- a/po/fi.po +++ b/po/fi.po @@ -212,8 +212,8 @@ msgid "Notify of idle" msgstr "Ilmoita joutenolosta" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(Vain Gnome)" +msgid "(Wayland only)" +msgstr "(Vain Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/po/fr.po b/po/fr.po index 4a9e95a..262f995 100644 --- a/po/fr.po +++ b/po/fr.po @@ -200,8 +200,8 @@ msgid "Notify of idle" msgstr "Notification d'inactivité" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(Uniquement pour GNOME)" +msgid "(Wayland only)" +msgstr "(Uniquement pour Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/po/he.po b/po/he.po index dc97434..3f4f309 100644 --- a/po/he.po +++ b/po/he.po @@ -173,7 +173,7 @@ msgid "Notify of idle" msgstr "" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" +msgid "(Wayland only)" msgstr "" #: src/gtk/preferences_window.ui:38 diff --git a/po/it.po b/po/it.po index 9ba09dc..5a551bd 100644 --- a/po/it.po +++ b/po/it.po @@ -212,8 +212,8 @@ msgid "Notify of idle" msgstr "Notifiche inattività" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(Solo per GNOME)" +msgid "(Wayland only)" +msgstr "(Solo per Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/po/nb.po b/po/nb.po index b2619ff..d846a53 100644 --- a/po/nb.po +++ b/po/nb.po @@ -152,7 +152,7 @@ msgid "Notify of idle" msgstr "" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" +msgid "(Wayland only)" msgstr "" #: src/gtk/preferences_window.ui:38 diff --git a/po/nl.po b/po/nl.po index 11d8ad7..b8d260f 100644 --- a/po/nl.po +++ b/po/nl.po @@ -164,8 +164,8 @@ msgid "Notify of idle" msgstr "Melding tonen bij inactiviteit" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(werkt alleen op GNOME)" +msgid "(Wayland only)" +msgstr "(werkt alleen op Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/po/pt.po b/po/pt.po index f0ecc56..c3d5013 100644 --- a/po/pt.po +++ b/po/pt.po @@ -257,8 +257,8 @@ msgid "Notify of idle" msgstr "Notificação de inatividade" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(apenas GNOME)" +msgid "(Wayland only)" +msgstr "(apenas Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/po/ru.po b/po/ru.po index 7e12c6c..4c00882 100644 --- a/po/ru.po +++ b/po/ru.po @@ -135,8 +135,8 @@ msgid "Notify of idle" msgstr "Уведомлять о бездействии" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(только для GNOME)" +msgid "(Wayland only)" +msgstr "(только для Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/po/sk.po b/po/sk.po index f6afc6b..97be639 100644 --- a/po/sk.po +++ b/po/sk.po @@ -151,8 +151,8 @@ msgid "Notify of idle" msgstr "Upozorniť na nečinnosť" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(Len pre GNOME)" +msgid "(Wayland only)" +msgstr "(Len pre Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/po/tr.po b/po/tr.po index ccc939d..db916ba 100644 --- a/po/tr.po +++ b/po/tr.po @@ -120,8 +120,8 @@ msgid "Notify of idle" msgstr "Boştayı bildir" #: src/gtk/preferences_window.ui:33 -msgid "(GNOME only)" -msgstr "(Sadece GNOME)" +msgid "(Wayland only)" +msgstr "(Sadece Wayland)" #: src/gtk/preferences_window.ui:38 msgid "_Minutes to idle" diff --git a/src/gtk/preferences_dialog.ui b/src/gtk/preferences_dialog.ui index 2971d88..8def8cb 100644 --- a/src/gtk/preferences_dialog.ui +++ b/src/gtk/preferences_dialog.ui @@ -24,7 +24,7 @@ <child> <object class="AdwExpanderRow" id="notify_of_idle_expander"> <property name="title" translatable="yes">_Notify of Idle</property> - <property name="subtitle" translatable="yes">(GNOME Only)</property> + <property name="subtitle" translatable="yes">(Wayland only)</property> <property name="show_enable_switch">True</property> <property name="use_underline">True</property> <child> diff --git a/src/helpers/idle.rs b/src/helpers/idle.rs new file mode 100644 index 0000000..074eba9 --- /dev/null +++ b/src/helpers/idle.rs @@ -0,0 +1,120 @@ +// Furtherance - Track your time without being tracked +// Copyright (C) 2024 Ricky Kresslein <rk@unobserved.io> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use std::env; +use std::time::Duration; + +#[cfg(target_os = "linux")] +use { + std::path::Path, std::sync::Arc +}; + +pub fn get_mac_windows_x11_idle_seconds() -> u64 { + 0 // unimplemented +} + +pub fn get_idle_time() -> Result<u64, Box<dyn std::error::Error>> { + match env::consts::OS { + "windows" => Ok(get_mac_windows_x11_idle_seconds()), + "macos" => Ok(get_mac_windows_x11_idle_seconds()), + #[cfg(target_os = "linux")] + "linux" => { + if is_wayland() { + if is_gnome() { + get_gnome_idle_sync() + } else { + get_wayland_idle_sync() + } + } else if is_x11() { + Ok(get_mac_windows_x11_idle_seconds()) + } else { + Ok(0) + } + } + _ => Ok(0), + } +} + +#[cfg(target_os = "linux")] +fn is_wayland() -> bool { + if let Ok(_) = env::var("XDG_SESSION_TYPE").map(|v| v == "wayland") { + return true; + } else if let Ok(display) = env::var("WAYLAND_DISPLAY") { + if display.chars().next() == Some('/') { + return Path::new(&display).exists(); + } + if let Ok(runtime_dir) = env::var("XDG_RUNTIME_DIR") { + return Path::new( + &format!("{}/{}", runtime_dir, display) + ).exists(); + } + } + false +} + +#[cfg(target_os = "linux")] +fn is_x11() -> bool { + x11rb::connect(None).is_ok() +} + +#[cfg(target_os = "linux")] +fn get_wayland_idle_sync() -> Result<u64, Box<dyn std::error::Error>> { + use crate::helpers::wayland_idle; + + wayland_idle::initialize_wayland().unwrap(); + + Ok(wayland_idle::get_idle_time()) +} + +#[cfg(target_os = "linux")] +fn get_gnome_idle_sync() -> Result<u64, Box<dyn std::error::Error>> { + use dbus::blocking::stdintf::org_freedesktop_dbus::Properties; + let c = dbus::blocking::Connection::new_session()?; + + let p = c.with_proxy( + "org.gnome.Mutter.IdleMonitor", + "/org/gnome/Mutter/IdleMonitor/Core", + Duration::from_millis(5000), + ); + let (idle_time,): (u64,) = + p.method_call("org.gnome.Mutter.IdleMonitor", "GetIdletime", ())?; + Ok(idle_time / 1000) +} + +#[cfg(target_os = "linux")] +pub fn is_kde() -> bool { + if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") { + return desktop.to_uppercase().contains("KDE"); + } + false +} + +#[cfg(target_os = "linux")] +fn is_gnome() -> bool { + if let Ok(xdg_current_desktop) = env::var("XDG_CURRENT_DESKTOP") { + if xdg_current_desktop.to_lowercase().contains("gnome") { + return true; + } + } + + if let Ok(gdm_session) = env::var("GDMSESSION") { + if gdm_session.to_lowercase().contains("gnome") { + return true; + } + } + + false +} diff --git a/src/helpers/wayland_idle.rs b/src/helpers/wayland_idle.rs new file mode 100644 index 0000000..ae2bce7 --- /dev/null +++ b/src/helpers/wayland_idle.rs @@ -0,0 +1,348 @@ +// Furtherance - Track your time without being tracked +// Copyright (C) 2024 Ricky Kresslein <rk@unobserved.io> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, Mutex, +}; +use std::thread; +use wayland_client::protocol::wl_registry::{self, WlRegistry}; +use wayland_client::protocol::wl_seat::WlSeat; +use wayland_client::{Connection, Dispatch, QueueHandle}; +use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notification_v1::{ + self, ExtIdleNotificationV1, +}; +use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notifier_v1::ExtIdleNotifierV1; +use wayland_protocols_plasma::idle::client::org_kde_kwin_idle::OrgKdeKwinIdle; +use wayland_protocols_plasma::idle::client::org_kde_kwin_idle_timeout::{ + self, OrgKdeKwinIdleTimeout, +}; + +struct IdleState { + idle_since: Option<std::time::Instant>, +} + +impl IdleState { + fn new() -> Self { + Self { idle_since: None } + } +} + +lazy_static::lazy_static! { + static ref IDLE_STATE: Arc<Mutex<IdleState>> = Arc::new(Mutex::new(IdleState::new())); + static ref WAYLAND_INITIALIZED: Arc<Mutex<bool>> = Arc::new(Mutex::new(false)); + static ref MONITOR_RUNNING: Arc<AtomicBool> = Arc::new(AtomicBool::new(false)); + static ref STOP_SIGNAL: Arc<Mutex<Option<Sender<()>>>> = Arc::new(Mutex::new(None)); +} + +enum IdleManager { + Kde(OrgKdeKwinIdle), + Standard(ExtIdleNotifierV1), +} + +struct WaylandState { + idle_state: Arc<Mutex<IdleState>>, + seats: HashMap<u32, WlSeat>, + idle_manager: Option<IdleManager>, +} + +impl WaylandState { + fn new(idle_state: Arc<Mutex<IdleState>>) -> Self { + Self { + idle_state, + seats: HashMap::new(), + idle_manager: None, + } + } + + fn handle_global( + &mut self, + registry: &WlRegistry, + name: u32, + interface: String, + version: u32, + qh: &QueueHandle<Self>, + ) { + match &interface[..] { + "wl_seat" => { + let seat = registry.bind::<WlSeat, _, _>(name, version, qh, ()); + if let Some(idle_manager) = &self.idle_manager { + let timeout_ms = 1000; // 1 second + match idle_manager { + IdleManager::Kde(manager) => { + let _timeout = manager.get_idle_timeout(&seat, timeout_ms, qh, ()); + } + IdleManager::Standard(manager) => { + let _notification = + manager.get_idle_notification(timeout_ms, &seat, qh, name); + } + } + } + self.seats.insert(name, seat); + } + "org_kde_kwin_idle" => { + let idle_manager: OrgKdeKwinIdle = registry.bind(name, version, qh, ()); + // Set up idle timeouts for existing seats + for (_, seat) in &self.seats { + let _timeout = idle_manager.get_idle_timeout(seat, 1000, qh, ()); + } + self.idle_manager = Some(IdleManager::Kde(idle_manager)); + } + "ext_idle_notifier_v1" => { + let idle_manager: ExtIdleNotifierV1 = registry.bind(name, version, qh, ()); + // Set up idle notifications for existing seats + for (name, seat) in &self.seats { + let _notification = idle_manager.get_idle_notification(1000, seat, qh, *name); + } + self.idle_manager = Some(IdleManager::Standard(idle_manager)); + } + _ => {} + } + } +} + +impl Dispatch<WlRegistry, ()> for WaylandState { + fn event( + state: &mut Self, + registry: &WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle<Self>, + ) { + match event { + wl_registry::Event::Global { + name, + interface, + version, + } => state.handle_global(registry, name, interface, version, qh), + _ => {} + } + } +} + +impl Dispatch<OrgKdeKwinIdleTimeout, ()> for WaylandState { + fn event( + state: &mut Self, + _proxy: &OrgKdeKwinIdleTimeout, + event: org_kde_kwin_idle_timeout::Event, + _data: &(), + _conn: &Connection, + _qh: &QueueHandle<Self>, + ) { + match event { + org_kde_kwin_idle_timeout::Event::Idle => { + if let Ok(mut state) = state.idle_state.lock() { + state.idle_since = Some(std::time::Instant::now()); + } + } + org_kde_kwin_idle_timeout::Event::Resumed => { + if let Ok(mut state) = state.idle_state.lock() { + state.idle_since = None; + } + } + _ => {} + } + } +} + +impl Dispatch<ExtIdleNotificationV1, u32> for WaylandState { + fn event( + state: &mut Self, + _proxy: &ExtIdleNotificationV1, + event: ext_idle_notification_v1::Event, + _data: &u32, + _conn: &Connection, + _qh: &QueueHandle<Self>, + ) { + match event { + ext_idle_notification_v1::Event::Idled => { + if let Ok(mut state) = state.idle_state.lock() { + state.idle_since = Some(std::time::Instant::now()); + } + } + ext_idle_notification_v1::Event::Resumed => { + if let Ok(mut state) = state.idle_state.lock() { + state.idle_since = None; + } + } + _ => {} + } + } +} + +impl Dispatch<WlSeat, ()> for WaylandState { + fn event( + _state: &mut Self, + _proxy: &WlSeat, + _event: <WlSeat as wayland_client::Proxy>::Event, + _data: &(), + _conn: &Connection, + _qh: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<OrgKdeKwinIdle, ()> for WaylandState { + fn event( + _state: &mut Self, + _proxy: &OrgKdeKwinIdle, + _event: <OrgKdeKwinIdle as wayland_client::Proxy>::Event, + _data: &(), + _conn: &Connection, + _qh: &QueueHandle<Self>, + ) { + } +} + +impl Dispatch<ExtIdleNotifierV1, ()> for WaylandState { + fn event( + _state: &mut Self, + _proxy: &ExtIdleNotifierV1, + _event: <ExtIdleNotifierV1 as wayland_client::Proxy>::Event, + _data: &(), + _conn: &Connection, + _qh: &QueueHandle<Self>, + ) { + } +} + +fn run_wayland_monitor(rx: Receiver<()>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { + let conn = Connection::connect_to_env()?; + let mut event_queue = conn.new_event_queue(); + let qh = event_queue.handle(); + + let display = conn.display(); + display.get_registry(&qh, ()); + + let state = WaylandState::new(IDLE_STATE.clone()); + let mut state = state; + + loop { + if !MONITOR_RUNNING.load(Ordering::SeqCst) { + break; + } + + // Check if we received a stop signal + if rx.try_recv().is_ok() { + break; + } + + event_queue.blocking_dispatch(&mut state)?; + thread::sleep(std::time::Duration::from_millis(100)); + } + Ok(()) +} + +pub fn initialize_wayland() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { + if let Ok(mut initialized) = WAYLAND_INITIALIZED.lock() { + if *initialized { + return Ok(()); + } + + MONITOR_RUNNING.store(true, Ordering::SeqCst); + + // Create a channel for stop signaling + let (tx, rx) = channel(); + if let Ok(mut stop_signal) = STOP_SIGNAL.lock() { + *stop_signal = Some(tx); + } + + thread::spawn(move || { + if let Err(e) = run_wayland_monitor(rx) { + eprintln!("Wayland monitor error: {}", e); + } + }); + + *initialized = true; + } + Ok(()) +} + +pub fn get_idle_time() -> u64 { + if !MONITOR_RUNNING.load(Ordering::SeqCst) { + return 0; + } + + if let Ok(state) = IDLE_STATE.lock() { + if let Some(idle_since) = state.idle_since { + idle_since.elapsed().as_secs() + } else { + 0 + } + } else { + 0 + } +} + +pub fn start_idle_monitor() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { + // Stop any existing monitor and wait for confirmation + stop_idle_monitor(); + + // Reset idle state + if let Ok(mut state) = IDLE_STATE.lock() { + state.idle_since = None; + } + + if let Ok(mut initialized) = WAYLAND_INITIALIZED.lock() { + if !*initialized { + MONITOR_RUNNING.store(true, Ordering::SeqCst); + + // Create a channel for stop signaling + let (tx, rx) = channel(); + if let Ok(mut stop_signal) = STOP_SIGNAL.lock() { + *stop_signal = Some(tx); + } + + thread::spawn(move || { + if let Err(e) = run_wayland_monitor(rx) { + eprintln!("Wayland monitor error: {}", e); + } + }); + + *initialized = true; + } + } + Ok(()) +} + +pub fn stop_idle_monitor() { + MONITOR_RUNNING.store(false, Ordering::SeqCst); + + // Signal the monitor thread to stop + if let Ok(stop_signal) = STOP_SIGNAL.lock() { + if let Some(tx) = stop_signal.as_ref() { + let _ = tx.send(()); + } + } + + // Reset idle state + if let Ok(mut state) = IDLE_STATE.lock() { + state.idle_since = None; + } + + // Reset initialized state + if let Ok(mut initialized) = WAYLAND_INITIALIZED.lock() { + *initialized = false; + } + + // Clear the stop signal + if let Ok(mut stop_signal) = STOP_SIGNAL.lock() { + *stop_signal = None; + } +} diff --git a/src/main.rs b/src/main.rs index abedf64..ac51761 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,12 @@ mod config; mod database; mod settings_manager; mod ui; +mod helpers { + mod idle; + mod wayland_idle; + + pub use idle::get_idle_time; +} use self::application::FurtheranceApplication; diff --git a/src/ui/window.rs b/src/ui/window.rs index bf23729..7fd4450 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -583,17 +583,7 @@ impl FurtheranceWindow { } fn get_idle_time(&self) -> Result<u64, Box<dyn std::error::Error>> { - let c = Connection::new_session()?; - - let p = c.with_proxy( - "org.gnome.Mutter.IdleMonitor", - "/org/gnome/Mutter/IdleMonitor/Core", - Duration::from_millis(5000), - ); - let (idle_time,): (u64,) = - p.method_call("org.gnome.Mutter.IdleMonitor", "GetIdletime", ())?; - - Ok(idle_time / 1000) + crate::helpers::get_idle_time() } fn check_user_idle(&self) { |