diff --git a/pkgs/afk-cmds/afk-cmds/.vscode/launch.json b/pkgs/afk-cmds/afk-cmds/.vscode/launch.json
new file mode 100644
index 0000000..47a2bb2
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/.vscode/launch.json
@@ -0,0 +1,45 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Debug executable 'afk-cmds'",
+ "cargo": {
+ "args": [
+ "run",
+ "--bin=afk-cmds",
+ "--package=afk-cmds"
+ ],
+ "filter": {
+ "name": "afk-cmds",
+ "kind": "bin"
+ }
+ },
+ "args": [],
+ "cwd": "${workspaceFolder}"
+ },
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Debug unit tests in executable 'afk-cmds'",
+ "cargo": {
+ "args": [
+ "test",
+ "--no-run",
+ "--bin=afk-cmds",
+ "--package=afk-cmds"
+ ],
+ "filter": {
+ "name": "afk-cmds",
+ "kind": "bin"
+ }
+ },
+ "args": [],
+ "cwd": "${workspaceFolder}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pkgs/afk-cmds/afk-cmds/.vscode/settings.json b/pkgs/afk-cmds/afk-cmds/.vscode/settings.json
new file mode 100644
index 0000000..a03ffe4
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "nixEnvSelector.suggestion": false
+}
\ No newline at end of file
diff --git a/pkgs/afk-cmds/afk-cmds/Cargo.lock b/pkgs/afk-cmds/afk-cmds/Cargo.lock
new file mode 100644
index 0000000..74e46c5
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/Cargo.lock
@@ -0,0 +1,800 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "afk-cmds"
+version = "0.1.0"
+dependencies = [
+ "ctrlc",
+ "gtk",
+ "libappindicator",
+ "serde",
+ "serde_json",
+ "single-instance",
+ "subprocess",
+ "winapi",
+ "x11",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
+
+[[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 = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[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 = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
+[[package]]
+name = "cfg-expr"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "ctrlc"
+version = "3.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173"
+dependencies = [
+ "nix 0.25.0",
+ "winapi",
+]
+
+[[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 = "futures-channel"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
+
+[[package]]
+name = "futures-task"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
+
+[[package]]
+name = "futures-util"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "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 = "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 = "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",
+ "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 = "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 = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "itoa"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
+
+[[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.132"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
+
+[[package]]
+name = "libloading"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "nix"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nix"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
+dependencies = [
+ "autocfg",
+ "bitflags",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
+
+[[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 = "pest"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048"
+dependencies = [
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
+dependencies = [
+ "once_cell",
+ "thiserror",
+ "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.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+
+[[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.144"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.144"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "single-instance"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4637485391f8545c9d3dbf60f9d9aab27a90c789a700999677583bcb17c8795d"
+dependencies = [
+ "libc",
+ "nix 0.23.1",
+ "thiserror",
+ "widestring",
+ "winapi",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
+
+[[package]]
+name = "subprocess"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709"
+dependencies = [
+ "cfg-expr",
+ "heck",
+ "pkg-config",
+ "toml",
+ "version-compare",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "widestring"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "x11"
+version = "2.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7ae97874a928d821b061fce3d1fc52f08071dd53c89a6102bc06efcac3b2908"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
diff --git a/pkgs/afk-cmds/afk-cmds/Cargo.toml b/pkgs/afk-cmds/afk-cmds/Cargo.toml
new file mode 100644
index 0000000..b0baba5
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "afk-cmds"
+version = "0.1.0"
+edition = "2021"
+build = "build.rs"
+
+[target.'cfg(windows)'.dependencies]
+winapi = { version = "0.3.8", features = ["winuser", "sysinfoapi", "errhandlingapi"] }
+[target.'cfg(unix)'.dependencies]
+x11 = {version = "2.20.0" }
+gtk = {version = "0.15.5"}
+libappindicator = {version = "0.7.1"}
+[dependencies]
+serde_json = { version = "1.0.85" }
+serde = {version = "1.0.144", features = ["derive"]}
+ctrlc = { version = "3.0", features = ["termination"] }
+single-instance = "0.3.3"
+subprocess = "0.2.9"
+[profile.release]
+strip = true
+opt-level = "z"
+lto = true
+codegen-units = 1
+panic = "abort"
+
diff --git a/pkgs/afk-cmds/afk-cmds/LICENSE b/pkgs/afk-cmds/afk-cmds/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/pkgs/afk-cmds/afk-cmds/afk-cmds.iml b/pkgs/afk-cmds/afk-cmds/afk-cmds.iml
new file mode 100644
index 0000000..2fecef3
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/afk-cmds.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pkgs/afk-cmds/afk-cmds/afk-icon.png b/pkgs/afk-cmds/afk-cmds/afk-icon.png
new file mode 100644
index 0000000..7270399
Binary files /dev/null and b/pkgs/afk-cmds/afk-cmds/afk-icon.png differ
diff --git a/pkgs/afk-cmds/afk-cmds/build.rs b/pkgs/afk-cmds/afk-cmds/build.rs
new file mode 100644
index 0000000..7444598
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/build.rs
@@ -0,0 +1,6 @@
+fn main() {
+#[cfg(windows)]
+return;
+println!("cargo:rustc-link-lib=X11");
+println!("cargo:rustc-link-lib=Xss");
+}
diff --git a/pkgs/afk-cmds/afk-cmds/shell.nix b/pkgs/afk-cmds/afk-cmds/shell.nix
new file mode 100644
index 0000000..41bd46d
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/shell.nix
@@ -0,0 +1,18 @@
+let
+pkgs = import {};
+in
+with pkgs;
+mkShell rec {
+ buildInputs = with pkgs; [
+ xorg.libXScrnSaver
+ xorg.libX11
+ # Dev dependencies
+ rustup
+ pkg-config
+ gdk-pixbuf
+ gtkmm3
+ libappindicator-gtk3
+ ];
+ LD_LIBRARY_PATH = "${lib.makeLibraryPath buildInputs}";
+ RUST_BACKTRACE = 1;
+}
diff --git a/pkgs/afk-cmds/afk-cmds/src/main.rs b/pkgs/afk-cmds/afk-cmds/src/main.rs
new file mode 100644
index 0000000..f759c1c
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/src/main.rs
@@ -0,0 +1,213 @@
+#![windows_subsystem = "windows"]
+
+use ctrlc;
+use ctrlc::set_handler;
+use serde::{Deserialize, Serialize};
+use single_instance::SingleInstance;
+use std::sync::Arc;
+use subprocess::{Popen, PopenConfig, Redirection};
+
+use std::env;
+use std::env::current_exe;
+use std::ffi::OsString;
+use std::fs::File;
+use std::io::Read;
+use std::path::PathBuf;
+use std::process::exit;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::thread::sleep;
+use std::time::Duration;
+use std::vec;
+#[cfg(unix)]
+use unix::*;
+#[cfg(windows)]
+use windows::*;
+
+#[cfg(unix)]
+mod unix;
+#[cfg(windows)]
+mod windows;
+
+mod tray_icon;
+use tray_icon::*;
+#[derive(Deserialize, Serialize, Debug, Clone)]
+pub struct ProcessInfo {
+ enabled: bool,
+ path: String,
+ cwd: String,
+ args: Vec,
+ show_window: bool,
+ while_afk: bool,
+ kill: bool,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+struct Jason {
+ process_info: Vec,
+ afk_seconds: u64,
+}
+
+fn read_config() -> Jason {
+ let mut config_path = Default::default();
+ let args: Vec = env::args_os().collect();
+ if !args.is_empty() {
+ match args
+ .iter()
+ .position(|r| (r == &OsString::from("--config") || r == &OsString::from("-c")))
+ {
+ Some(x) => {
+ if x < args.len() {
+ config_path = PathBuf::from(&args[x + 1 as usize]);
+ } else {
+ println!("please specify a config file");
+ }
+ }
+ None => {
+ config_path = current_exe().expect("Could not get exe location to find config.json");
+ config_path.pop();
+ config_path.push("config.json");
+ }
+ };
+ }
+ if config_path.exists() {
+ let mut data = String::new();
+ File::open(config_path)
+ .unwrap()
+ .read_to_string(&mut data)
+ .unwrap();
+ return serde_json::from_str(&data).expect("Invalid config format");
+ } else {
+ println! {"{}","Couldn't read config"};
+ exit(1);
+ }
+}
+
+fn start(mut group: &mut Group, movable: i32) {
+ if group.started {
+ return;
+ }
+ group.started = true;
+ let mut i = 0;
+ for info in group.process_info.iter() {
+ let mut argv = vec![OsString::from(&info.path)];
+ for arg in &info.args {
+ argv.push(OsString::from(arg));
+ }
+ let mut child = Popen::create(
+ &argv,
+ PopenConfig {
+ cwd: Option::from(OsString::from(&info.cwd)),
+ stdin: Redirection::Pipe,
+ ..Default::default()
+ },
+ )
+ .expect("couldn't start child ");
+ sleep(Duration::from_secs(1));
+
+ if info.while_afk {
+ if info.show_window {
+ move_window(child.pid().expect("couldn't get pid"), i, movable);
+ i += 1
+ } else {
+ hide_window(child.pid().expect("couldn't get pid"));
+ }
+ }
+ if info.kill {
+ group.children.push(child);
+ } else {
+ child.detach();
+ }
+ }
+}
+
+fn stop(mut group: &mut Group) {
+ if !group.started {
+ return;
+ }
+ group.started = false;
+ let children = &mut group.children;
+ for i in 0..children.len() {
+ children[i].terminate().expect("Couldn't exit process");
+ let _ = children[i].wait_timeout(Duration::from_secs(1));
+ }
+ if !children.is_empty() {
+ children.clear();
+ }
+}
+
+struct Group {
+ process_info: Vec,
+ children: Vec,
+ started: bool,
+}
+
+fn main() {
+ wait_for_display();
+ let running_arc = Arc::new(AtomicBool::new(true));
+ let pause_arc = Arc::new(AtomicBool::new(false));
+ let r_clone = running_arc.clone();
+ let p_clone = pause_arc.clone();
+
+ let instance = SingleInstance::new("afk-cmds").unwrap();
+ if !instance.is_single() {
+ println!("Another instance is running");
+ exit(1);
+ }
+ let mut json = read_config();
+ tray_icon(r_clone, p_clone);
+ //catch ctrl-c as gracefully as possible
+ let running_clone = running_arc.clone();
+ set_handler(move || {
+ println!("{}", "Exit signal received exiting....");
+ running_clone.store(false, Ordering::Relaxed)
+ })
+ .unwrap();
+
+ let mut afk = Group {
+ process_info: vec![],
+ children: vec![],
+ started: false,
+ };
+ let mut active = Group {
+ process_info: vec![],
+ children: vec![],
+ started: false,
+ };
+ let mut movable = 0;
+ for info in &json.process_info {
+ if !info.enabled {
+ continue;
+ }
+ if info.while_afk {
+ afk.process_info.push(info.clone());
+ if info.show_window {
+ movable += 1;
+ }
+ } else {
+ active.process_info.push(info.clone());
+ }
+ }
+ json.process_info.clear();
+ println!("{}", "Started");
+ while running_arc.load(Ordering::Relaxed) {
+ if pause_arc.load(Ordering::Relaxed) {
+ sleep(Duration::from_secs(1));
+ continue;
+ };
+
+ let idle_time = get_idle_time();
+ if idle_time >= json.afk_seconds {
+ stop(&mut active);
+ start(&mut afk, movable);
+ } else {
+ stop(&mut afk);
+ start(&mut active, 0);
+ sleep(Duration::from_secs(1))
+ }
+ sleep(Duration::from_secs(1));
+ }
+ stop(&mut afk);
+ stop(&mut active);
+ println!("{}", "Exited normally");
+ exit(0);
+}
diff --git a/pkgs/afk-cmds/afk-cmds/src/tray_icon.rs b/pkgs/afk-cmds/afk-cmds/src/tray_icon.rs
new file mode 100644
index 0000000..e4d60b5
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/src/tray_icon.rs
@@ -0,0 +1,29 @@
+use std::sync::Arc;
+use core::sync::atomic::{AtomicBool,Ordering};
+use gtk::prelude::*;
+use libappindicator::{AppIndicator, AppIndicatorStatus};
+use std::thread;
+
+pub fn tray_icon(running_arc:Arc, pause_arc:Arc) {
+ thread::spawn(move || {
+ gtk::init().unwrap();
+ let mut indicator = AppIndicator::new("afk-cmds", ""); //temporary logo
+ indicator.set_status(AppIndicatorStatus::Active);
+ let mut menu = gtk::Menu::new();
+ let pause = gtk::CheckMenuItem::with_label("Paused");
+ pause.connect_activate(move |_| {
+ pause_arc.store(!pause_arc.load(Ordering::Relaxed), Ordering::Relaxed);
+ });
+ menu.append(&pause);
+
+ let quit = gtk::MenuItem::with_label("Quit");
+ quit.connect_activate(move |_| {
+ running_arc.store(false, Ordering::Relaxed);
+ });
+ menu.append(&quit);
+
+ indicator.set_menu(&mut menu);
+ menu.show_all();
+ gtk::main()
+ });
+}
diff --git a/pkgs/afk-cmds/afk-cmds/src/unix-cfg.json b/pkgs/afk-cmds/afk-cmds/src/unix-cfg.json
new file mode 100644
index 0000000..16519c1
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/src/unix-cfg.json
@@ -0,0 +1,17 @@
+{
+ "process_info": [
+ {
+ "enabled": true,
+ "path": "/run/current-system/sw/bin/st",
+ "cwd": "/home/gerg/",
+ "args": [
+ "ping",
+ "1.1.1.1"
+ ],
+ "show_window": true,
+ "while_afk": true,
+ "kill": true
+ }
+ ],
+ "afk_seconds": 3
+}
\ No newline at end of file
diff --git a/pkgs/afk-cmds/afk-cmds/src/unix.rs b/pkgs/afk-cmds/afk-cmds/src/unix.rs
new file mode 100644
index 0000000..4994523
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/src/unix.rs
@@ -0,0 +1,162 @@
+use x11::xlib::{
+ Atom, CWOverrideRedirect, Display, Window, XChangeWindowAttributes, XCloseDisplay,
+ XDefaultRootWindow, XDefaultScreen, XDisplayHeight, XDisplayWidth, XFlush, XFree,
+ XGetWindowProperty, XInternAtom, XMapWindow, XMoveResizeWindow, XOpenDisplay, XQueryTree,
+ XSetWindowAttributes, XUnmapWindow, XA_CARDINAL,
+};
+use x11::xss::{XScreenSaverAllocInfo, XScreenSaverQueryInfo};
+
+use std::ffi::c_void;
+use std::os::raw::c_int;
+use std::ptr::null;
+use std::thread;
+use std::time::Duration;
+
+pub fn get_idle_time() -> u64 {
+ unsafe {
+ let dpy = XOpenDisplay(null());
+ if dpy.is_null() {
+ println!("{}","Couldn't access display in fn get_idle_time()");
+ thread::sleep(Duration::from_secs(10));
+ return 0;
+ }
+ let info = XScreenSaverAllocInfo();
+ XScreenSaverQueryInfo(dpy, XDefaultRootWindow(dpy), info);
+ let r = (*info).idle;
+ XFree(info as *mut c_void);
+ XCloseDisplay(dpy);
+ return (r / 1000) as u64;
+ }
+}
+
+pub fn wait_for_display() {
+ loop {
+ unsafe {
+ let dpy = XOpenDisplay(null());
+ if !dpy.is_null() {
+ XCloseDisplay(dpy);
+ println!("{}","Starting...");
+ return;
+ }
+ println!("{}", "Waiting for X");
+ thread::sleep(Duration::from_secs(10));
+ }
+ }
+}
+
+pub fn move_window(pid: u32, num: i32, count: i32) {
+ let _ = pid;
+ unsafe {
+ let dpy: *mut Display = XOpenDisplay(null());
+ if dpy.is_null() {
+ println!("{}","Couldn't access display in fn move_window()");
+ return;
+ }
+ let scr: c_int = XDefaultScreen(dpy);
+ let width: c_int = XDisplayWidth(dpy, scr) / count;
+ let height: c_int = XDisplayHeight(dpy, scr);
+ let window = get_by_pid(dpy, pid);
+ if window == 0 {
+ print!("{}","Couldn't move window");
+ XCloseDisplay(dpy);
+ return;
+ }
+ let mut wattr: XSetWindowAttributes = std::mem::zeroed();
+ wattr.override_redirect = 1;
+ XChangeWindowAttributes(dpy, window, CWOverrideRedirect, &mut wattr);
+ XUnmapWindow(dpy, window);
+ XFlush(dpy);
+ XMapWindow(dpy, window);
+ XFlush(dpy);
+ XMoveResizeWindow(dpy, window, width * num, 0, width as u32, height as u32);
+ XCloseDisplay(dpy);
+ }
+}
+
+pub fn hide_window(pid: u32) {
+ let _ = pid;
+ unsafe {
+ let dpy: *mut Display = XOpenDisplay(std::ptr::null());
+ let window = get_by_pid(dpy, pid);
+ let mut wattr: XSetWindowAttributes = std::mem::zeroed();
+ wattr.override_redirect = 1;
+ XChangeWindowAttributes(dpy, window, CWOverrideRedirect, &mut wattr);
+ XUnmapWindow(dpy, window);
+ XFlush(dpy);
+ XCloseDisplay(dpy);
+ }
+}
+
+unsafe fn recursion(
+ display: *mut Display,
+ pid: u32,
+ atom_pid: Atom,
+ window: Window,
+ results: &mut Vec,
+) {
+ let mut typew: u64 = 0;
+ let mut format: i32 = 0;
+ let mut n_items: u64 = 0;
+ let mut bytes_after: u64 = 0;
+ let mut prop_pid: *mut u8 = 0 as *mut _;
+ if XGetWindowProperty(
+ display,
+ window,
+ atom_pid,
+ 0,
+ 1,
+ 0,
+ XA_CARDINAL,
+ &mut typew,
+ &mut format,
+ &mut n_items,
+ &mut bytes_after,
+ &mut prop_pid,
+ ) == 0
+ {
+ if prop_pid != 0 as *mut _ {
+ if pid as u64 == *(prop_pid as *const u64) {
+ results.push(window);
+ }
+
+ XFree(prop_pid as *mut c_void);
+ }
+ }
+ let mut root_window: u64 = 0;
+ let mut parent_window: u64 = 0;
+ let mut children: *mut u64 = 0 as *mut _;
+ let mut children_count: u32 = 0;
+ if XQueryTree(
+ display,
+ window,
+ &mut root_window,
+ &mut parent_window,
+ &mut children,
+ &mut children_count,
+ ) != 0
+ {
+ let slice = std::slice::from_raw_parts(children, children_count as usize);
+ for window in slice {
+ recursion(display, pid, atom_pid, *window, results);
+ }
+ }
+}
+
+unsafe fn get_by_pid(display: *mut Display, pid: u32) -> Window {
+ if display.is_null() {
+ return 0;
+ }
+
+ let root: Window = XDefaultRootWindow(display);
+ let atom = XInternAtom(display, "_NET_WM_PID\0".as_ptr() as *const i8, 1);
+ if atom == 0 {
+ return 0;
+ }
+
+ let mut results: Vec = Vec::new();
+ recursion(display, pid, atom, root, &mut results);
+ if results.is_empty() {
+ return 0;
+ }
+ return results[0];
+}
diff --git a/pkgs/afk-cmds/afk-cmds/src/win-cfg.json b/pkgs/afk-cmds/afk-cmds/src/win-cfg.json
new file mode 100644
index 0000000..b28b5f8
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/src/win-cfg.json
@@ -0,0 +1,17 @@
+{
+ "process_info": [
+ {
+ "enabled": true,
+ "path": "C:\\Windows\\system32\\cmd.exe",
+ "cwd": "C:\\",
+ "args": [
+ "/K",
+ "ping 1.1.1.1"
+ ],
+ "show_window": true,
+ "while_afk": true,
+ "kill": true
+ }
+ ],
+ "afk_seconds": 3
+}
\ No newline at end of file
diff --git a/pkgs/afk-cmds/afk-cmds/src/windows.rs b/pkgs/afk-cmds/afk-cmds/src/windows.rs
new file mode 100644
index 0000000..3bbd7c5
--- /dev/null
+++ b/pkgs/afk-cmds/afk-cmds/src/windows.rs
@@ -0,0 +1,100 @@
+use std::mem::size_of;
+use winapi;
+use winapi::shared::minwindef::{DWORD, LPARAM, LPDWORD, TRUE};
+use winapi::shared::windef::HWND;
+pub use winapi::shared::winerror::{ERROR_ACCESS_DENIED, ERROR_BAD_PATHNAME};
+use winapi::um::winuser::{
+ GetWindow, IsWindowVisible, SetWindowPos, GW_OWNER, HWND_BOTTOM, HWND_TOP, SWP_HIDEWINDOW,
+ SWP_NOSENDCHANGING, SWP_SHOWWINDOW,
+};
+use winapi::um::{
+ sysinfoapi::GetTickCount64,
+ winuser,
+ winuser::{GetLastInputInfo, GetSystemMetrics, LASTINPUTINFO, SM_CXSCREEN, SM_CYSCREEN},
+};
+
+pub fn get_idle_time() -> u64 {
+ unsafe {
+ let last_input = &mut LASTINPUTINFO {
+ cbSize: 0,
+ dwTime: 0,
+ } as *mut LASTINPUTINFO;
+ (*last_input).cbSize = size_of::() as u32;
+ let _ = GetLastInputInfo(last_input);
+ return (GetTickCount64() - ((*last_input).dwTime) as u64) / 1000;
+ }
+}
+
+pub fn move_window(pid: u32, num: i32, count: i32) {
+ unsafe {
+ let width = (GetSystemMetrics(SM_CXSCREEN)) / count;
+ let height = GetSystemMetrics(SM_CYSCREEN);
+ let window = find_window(pid);
+ SetWindowPos(
+ window,
+ HWND_TOP,
+ (width * num) - 7,
+ 0,
+ width + 15,
+ height,
+ SWP_SHOWWINDOW + SWP_NOSENDCHANGING,
+ );
+ }
+}
+
+pub fn hide_window(pid: u32) {
+ unsafe {
+ let window = find_window(pid);
+ SetWindowPos(
+ window,
+ HWND_BOTTOM,
+ 0,
+ 0,
+ 10,
+ 10,
+ SWP_HIDEWINDOW + SWP_NOSENDCHANGING,
+ );
+ }
+}
+
+struct HandleData {
+ process_id: LPDWORD,
+ window_handle: HWND,
+}
+
+fn is_main_window(handle: HWND) -> bool {
+ unsafe {
+ return GetWindow(handle, GW_OWNER) == 0 as HWND && (IsWindowVisible(handle) == TRUE);
+ }
+}
+
+unsafe fn find_window(pid: u32) -> HWND {
+ let mut data = HandleData {
+ process_id: pid as LPDWORD,
+ window_handle: 0 as HWND,
+ };
+ winuser::EnumWindows(
+ Some(enum_windows_proc),
+ &mut data as *mut HandleData as LPARAM,
+ );
+ return data.window_handle;
+}
+
+extern "system" fn enum_windows_proc(handle: HWND, l_param: LPARAM) -> i32 {
+ let data = unsafe { &mut *(l_param as *mut HandleData) };
+ let process_id: LPDWORD = 0 as LPDWORD;
+ unsafe { GetWindowThreadProcessId(handle, &process_id) };
+ if data.process_id != process_id || !is_main_window(handle) {
+ return 1;
+ }
+ data.window_handle = handle;
+ return 0;
+}
+
+extern "system" {
+ fn GetWindowThreadProcessId(hWnd: HWND, lpdwProcessId: &LPDWORD) -> DWORD;
+}
+
+pub fn wait_for_display() {
+ return;
+}
diff --git a/pkgs/afk-cmds/default.nix b/pkgs/afk-cmds/default.nix
index d8ad412..c06f37a 100644
--- a/pkgs/afk-cmds/default.nix
+++ b/pkgs/afk-cmds/default.nix
@@ -17,12 +17,7 @@ rustPlatform.buildRustPackage rec {
pname = "afk-cmds";
version= "1.0.0";
- src = fetchFromGitHub {
- owner = "ISnortPennies";
- repo = "afk-cmds";
- rev = "b345d5a038a86c6ca31d3bd8800ac759da912a22";
- sha256 = "sha256-yleq8bg3ZnilbYTNXRteBALiJ/fIXOxXxqf966OokqQ=";
- };
+ src = ./afk-cmds;
buildInputs = [
libX11