diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac966dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +.mypy_cache/ +.pytest_cache/ +/.project +/.pydevproject +/.settings +/.cproject +/.idea +/.vscode + +__pycache__ +/.coverage/ +/.coveragerc +/install dir +/work area + +/meson-test-run.txt +/meson-test-run.xml +/meson-cross-test-run.txt +/meson-cross-test-run.xml + +.DS_Store +*~ +*.swp +packagecache +.wraplock +/MANIFEST +/build +/dist +/meson.egg-info + +/docs/built_docs +/docs/hotdoc-private* + +*.pyc +/*venv* + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..be5b08a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1002 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahfail" +version = "0.1.0" +dependencies = [ + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gstreamer", + "gstreamer-player", + "gtk", + "libc", + "once_cell", + "pkg-config", + "rand", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[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 = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +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 = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" +dependencies = [ + "anyhow", + "heck 0.4.1", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 = "gstreamer" +version = "0.18.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66363bacf5e4f6eb281564adc2902e44c52ae5c45082423e7439e9012b75456" +dependencies = [ + "bitflags", + "cfg-if", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "gstreamer-sys", + "libc", + "muldiv", + "num-integer", + "num-rational", + "once_cell", + "option-operations", + "paste", + "pretty-hex", + "thiserror", +] + +[[package]] +name = "gstreamer-base" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224f35f36582407caf58ded74854526beeecc23d0cf64b8d1c3e00584ed6863f" +dependencies = [ + "bitflags", + "cfg-if", + "glib", + "gstreamer", + "gstreamer-base-sys", + "libc", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a083493c3c340e71fa7c66eebda016e9fafc03eb1b4804cf9b2bad61994b078e" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-player" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f14ee02352ba73cadebe640bfb33f12fe8d03cbcad816a102d55a0251fb99bb" +dependencies = [ + "bitflags", + "glib", + "gstreamer", + "gstreamer-player-sys", + "gstreamer-video", + "libc", + "once_cell", +] + +[[package]] +name = "gstreamer-player-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f9b674b39a4d0e18710f6e3d2b109f1793d8028ee4e39da3909b55b4529d399" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "gstreamer-video-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3517a65d3c2e6f8905b456eba5d53bda158d664863aef960b44f651cb7d33e2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-video" +version = "0.18.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9418adfc72dafa1ad9eb106527ce4804887d101027c4528ec28c7d29cc899519" +dependencies = [ + "bitflags", + "cfg-if", + "futures-channel", + "glib", + "gstreamer", + "gstreamer-base", + "gstreamer-video-sys", + "libc", + "once_cell", +] + +[[package]] +name = "gstreamer-video-sys" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33331b1675e73b5b000c796354278eca7fdde9327015971d9f41afe28b96e0dc" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-base-sys", + "gstreamer-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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "muldiv" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-operations" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b01597916c91a493b1e8a2fde64fec1764be3259abc1f06efc99c274f150a2" +dependencies = [ + "paste", +] + +[[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 = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty-hex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[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 1.0.109", + "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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.7.14", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[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 = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6709b7d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ahfail" +version = "0.1.0" +edition = "2021" + +[lib] +name = "ahfail_module" +crate-type = ["staticlib", "rlib"] + +[dependencies] +gtk = { version = "0.15", package = "gtk", features = ["v3_24"] } +gdk = { version = "0.15", package = "gdk", features = ["v3_24"] } +gstreamer = { version = "0.18", package = "gstreamer", features = ["v1_18"] } +gstreamer-player = { version = "0.18", package = "gstreamer-player" } +glib = { version = "0.15", package = "glib" } +gio = { version = "0.15", package = "gio" } +gdk-pixbuf = "0.15" +libc = "0.2" +once_cell = "1.10" +rand = "0.8" + +[build-dependencies] +pkg-config = "0.3" diff --git a/assets/ahfail.gresource.xml b/assets/ahfail.gresource.xml new file mode 100644 index 0000000..f28de01 --- /dev/null +++ b/assets/ahfail.gresource.xml @@ -0,0 +1,31 @@ + + + + magic-word.mp3 + frame__0000.png + frame__0001.png + frame__0002.png + frame__0003.png + frame__0004.png + frame__0005.png + frame__0006.png + frame__0007.png + frame__0008.png + frame__0009.png + frame__0010.png + frame__0011.png + frame__0012.png + frame__0013.png + frame__0014.png + frame__0015.png + frame__0016.png + frame__0017.png + frame__0018.png + frame__0019.png + frame__0020.png + frame__0021.png + frame__0022.png + frame__0023.png + frame__0024.png + + diff --git a/assets/frame__0000.png b/assets/frame__0000.png new file mode 100644 index 0000000..84efd92 Binary files /dev/null and b/assets/frame__0000.png differ diff --git a/assets/frame__0001.png b/assets/frame__0001.png new file mode 100644 index 0000000..0ff72b4 Binary files /dev/null and b/assets/frame__0001.png differ diff --git a/assets/frame__0002.png b/assets/frame__0002.png new file mode 100644 index 0000000..224e8a2 Binary files /dev/null and b/assets/frame__0002.png differ diff --git a/assets/frame__0003.png b/assets/frame__0003.png new file mode 100644 index 0000000..4085f53 Binary files /dev/null and b/assets/frame__0003.png differ diff --git a/assets/frame__0004.png b/assets/frame__0004.png new file mode 100644 index 0000000..d32d359 Binary files /dev/null and b/assets/frame__0004.png differ diff --git a/assets/frame__0005.png b/assets/frame__0005.png new file mode 100644 index 0000000..78b96f9 Binary files /dev/null and b/assets/frame__0005.png differ diff --git a/assets/frame__0006.png b/assets/frame__0006.png new file mode 100644 index 0000000..d5257a8 Binary files /dev/null and b/assets/frame__0006.png differ diff --git a/assets/frame__0007.png b/assets/frame__0007.png new file mode 100644 index 0000000..9664f22 Binary files /dev/null and b/assets/frame__0007.png differ diff --git a/assets/frame__0008.png b/assets/frame__0008.png new file mode 100644 index 0000000..767e4e2 Binary files /dev/null and b/assets/frame__0008.png differ diff --git a/assets/frame__0009.png b/assets/frame__0009.png new file mode 100644 index 0000000..b4aa2b9 Binary files /dev/null and b/assets/frame__0009.png differ diff --git a/assets/frame__0010.png b/assets/frame__0010.png new file mode 100644 index 0000000..1370a8c Binary files /dev/null and b/assets/frame__0010.png differ diff --git a/assets/frame__0011.png b/assets/frame__0011.png new file mode 100644 index 0000000..de91b41 Binary files /dev/null and b/assets/frame__0011.png differ diff --git a/assets/frame__0012.png b/assets/frame__0012.png new file mode 100644 index 0000000..e4a18e8 Binary files /dev/null and b/assets/frame__0012.png differ diff --git a/assets/frame__0013.png b/assets/frame__0013.png new file mode 100644 index 0000000..2150edd Binary files /dev/null and b/assets/frame__0013.png differ diff --git a/assets/frame__0014.png b/assets/frame__0014.png new file mode 100644 index 0000000..892cafc Binary files /dev/null and b/assets/frame__0014.png differ diff --git a/assets/frame__0015.png b/assets/frame__0015.png new file mode 100644 index 0000000..d685c9e Binary files /dev/null and b/assets/frame__0015.png differ diff --git a/assets/frame__0016.png b/assets/frame__0016.png new file mode 100644 index 0000000..4b3856f Binary files /dev/null and b/assets/frame__0016.png differ diff --git a/assets/frame__0017.png b/assets/frame__0017.png new file mode 100644 index 0000000..caafb5d Binary files /dev/null and b/assets/frame__0017.png differ diff --git a/assets/frame__0018.png b/assets/frame__0018.png new file mode 100644 index 0000000..66f7a23 Binary files /dev/null and b/assets/frame__0018.png differ diff --git a/assets/frame__0019.png b/assets/frame__0019.png new file mode 100644 index 0000000..78359fe Binary files /dev/null and b/assets/frame__0019.png differ diff --git a/assets/frame__0020.png b/assets/frame__0020.png new file mode 100644 index 0000000..46e4c11 Binary files /dev/null and b/assets/frame__0020.png differ diff --git a/assets/frame__0021.png b/assets/frame__0021.png new file mode 100644 index 0000000..3ae18b2 Binary files /dev/null and b/assets/frame__0021.png differ diff --git a/assets/frame__0022.png b/assets/frame__0022.png new file mode 100644 index 0000000..4e4d88a Binary files /dev/null and b/assets/frame__0022.png differ diff --git a/assets/frame__0023.png b/assets/frame__0023.png new file mode 100644 index 0000000..307e40a Binary files /dev/null and b/assets/frame__0023.png differ diff --git a/assets/frame__0024.png b/assets/frame__0024.png new file mode 100644 index 0000000..307e40a Binary files /dev/null and b/assets/frame__0024.png differ diff --git a/assets/magic-word.mp3 b/assets/magic-word.mp3 new file mode 100644 index 0000000..2e3750e Binary files /dev/null and b/assets/magic-word.mp3 differ diff --git a/include/ahfail/module.h b/include/ahfail/module.h new file mode 100644 index 0000000..7bf2fa0 --- /dev/null +++ b/include/ahfail/module.h @@ -0,0 +1,8 @@ +#pragma once + +#include "gtklock-module.h" + +extern const char module_name[]; +extern const guint module_major_version; +extern const guint module_minor_version; +extern GOptionEntry module_entries[]; diff --git a/include/gtklock-module.h b/include/gtklock-module.h new file mode 100644 index 0000000..14e2fb3 --- /dev/null +++ b/include/gtklock-module.h @@ -0,0 +1,100 @@ +// gtklock-userinfo-module +// Copyright (c) 2024 Jovan Lanik + +// Module header + +#include "gtk/gtk.h" + +struct Window { + GdkMonitor *monitor; + + GtkWidget *window; + GtkWidget *overlay; + GtkWidget *window_box; + GtkWidget *body_revealer; + GtkWidget *body_grid; + GtkWidget *input_label; + GtkWidget *input_field; + GtkWidget *message_revealer; + GtkWidget *message_scrolled_window; + GtkWidget *message_box; + GtkWidget *unlock_button; + GtkWidget *error_label; + GtkWidget *warning_label; + GtkWidget *info_box; + GtkWidget *time_box; + GtkWidget *clock_label; + GtkWidget *date_label; + + void *module_data[]; +}; + +struct GtkLock { + GtkApplication *app; + void *lock; + pid_t parent; + + GArray *windows; + GArray *messages; + GArray *errors; + + struct Window *focused_window; + gboolean hidden; + guint idle_timeout; + + guint draw_time_source; + guint idle_hide_source; + + gboolean follow_focus; + gboolean use_idle_hide; + + char *time; + char *date; + char *time_format; + char *date_format; + char *config_path; + char *layout_path; + char *lock_command; + char *unlock_command; + + GArray *modules; +}; + +const gchar *g_module_check_init(GModule *m); +void g_module_unload(GModule *m); +void on_activation(struct GtkLock *gtklock, int id); +void on_locked(struct GtkLock *gtklock); +void on_output_change(struct GtkLock *gtklock); +void on_focus_change(struct GtkLock *gtklock, struct Window *win, struct Window *old); +void on_idle_hide(struct GtkLock *gtklock); +void on_idle_show(struct GtkLock *gtklock); +void on_window_create(struct GtkLock *gtklock, struct Window *win); +void on_window_destroy(struct GtkLock *gtklock, struct Window *win); + +/* + +MIT Licence + +Copyright (c) 2024 Jovan Lanik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +MIT Licence + +*/ + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..75b2429 --- /dev/null +++ b/meson.build @@ -0,0 +1,53 @@ +project( + 'ahfail', + ['c', 'rust'], + version: '0.1.0', + meson_version: '>=1.3.0', + default_options: ['warning_level=3', 'c_std=c11'] +) + +ahfail_inc = include_directories('include') +cc = meson.get_compiler('c') +m_dep = cc.find_library('m', required : false) +gtk_dep = dependency('gtk+-3.0') +gstplayer_dep = dependency('gstreamer-player-1.0') +gnome = import('gnome') + +resources = gnome.compile_resources( + 'ahfail-resources', + 'assets/ahfail.gresource.xml', + source_dir: 'assets', + c_name: 'ahfail' +) + +cargo_target = custom_target( + 'ahfail-cargo-build', + input: ['src/lib.rs', 'Cargo.toml'], + output: ['libahfail_module.a'], + command: [ + 'sh', '-c', 'cargo build --release --target-dir "@OUTDIR@/target" && cp "@OUTDIR@/target/release/libahfail_module.a" "@OUTPUT@"' + ], + build_by_default: true +) + +libahfail = shared_library( + 'ahfail-module', + resources, + link_args: ['-Wl,--whole-archive', meson.current_build_dir() / 'libahfail_module.a', '-Wl,--no-whole-archive'], + link_depends: cargo_target, + include_directories: ahfail_inc, + dependencies: [gtk_dep, gstplayer_dep, m_dep], + install: true, + install_dir: get_option('libdir') / 'gtklock', + name_prefix: '' +) + +smoke = executable( + 'module_smoke_test', + 'tests/module_test.c', + include_directories: ahfail_inc, + link_with: libahfail, + dependencies: [gtk_dep, gstplayer_dep] +) + +test('module symbols', smoke) diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..797fdff --- /dev/null +++ b/src/config.rs @@ -0,0 +1,37 @@ +use gtk::{glib, gdk}; +use std::ffi::CStr; +use std::ptr; + +// Storage for the command line argument string +pub static mut DEADZONE_ARG: *mut std::os::raw::c_char = ptr::null_mut(); + +pub const DEADZONE_LONG: &[u8] = b"deadzone\0"; +pub const DEADZONE_DESC: &[u8] = b"Area to avoid spawning sprites (x,y,w,h)\0"; +pub const DEADZONE_ARG_DESC: &[u8] = b"x,y,w,h\0"; + +pub struct ModuleConfig { + pub deadzone: Option, +} + +impl ModuleConfig { + pub unsafe fn from_args() -> Self { + let mut deadzone = None; + if !DEADZONE_ARG.is_null() { + let c_str = CStr::from_ptr(DEADZONE_ARG); + if let Ok(s) = c_str.to_str() { + let parts: Vec<&str> = s.split(',').collect(); + if parts.len() == 4 { + if let (Ok(x), Ok(y), Ok(w), Ok(h)) = (parts[0].parse(), parts[1].parse(), parts[2].parse(), parts[3].parse()) { + deadzone = Some(gdk::Rectangle::new(x, y, w, h)); + println!("[ahfail] Configured deadzone: {:?}", deadzone); + } else { + eprintln!("[ahfail] Invalid numbers in deadzone argument: {}", s); + } + } else { + eprintln!("[ahfail] Invalid format for deadzone argument (expected x,y,w,h): {}", s); + } + } + } + Self { deadzone } + } +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..45a7bf0 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,110 @@ +use gtk::prelude::*; // Keep this if extension traits are needed, otherwise remove. Window uses it. +use gtk::{glib, gdk}; // Window uses gdk, glib. +use gtk::gdk::Monitor; +use std::ffi::c_void; +use std::marker::PhantomData; +use std::ptr; // Import ptr +use glib::translate::from_glib_none; +use crate::state::WindowData; + +#[repr(C)] +pub struct __IncompleteArrayField(PhantomData); + +impl __IncompleteArrayField { + pub const fn new() -> Self { + Self(PhantomData) + } + + /// # Safety + /// Returns a raw pointer to the field. The caller must ensure the struct was allocated + /// with enough space for this flexible array member. + pub unsafe fn as_ptr(&self) -> *mut T { + self as *const _ as *mut T + } +} + +impl Default for __IncompleteArrayField { + fn default() -> Self { + Self::new() + } +} + +#[repr(C)] +pub struct Window { + pub monitor: *mut gdk::ffi::GdkMonitor, + pub window: *mut gtk::ffi::GtkWidget, + pub overlay: *mut gtk::ffi::GtkOverlay, + pub window_box: *mut gtk::ffi::GtkWidget, + pub body_revealer: *mut gtk::ffi::GtkWidget, + pub body_grid: *mut gtk::ffi::GtkWidget, + pub input_label: *mut gtk::ffi::GtkWidget, + pub input_field: *mut gtk::ffi::GtkWidget, + pub message_revealer: *mut gtk::ffi::GtkWidget, + pub message_scrolled_window: *mut gtk::ffi::GtkWidget, + pub message_box: *mut gtk::ffi::GtkWidget, + pub unlock_button: *mut gtk::ffi::GtkWidget, + pub error_label: *mut gtk::ffi::GtkWidget, + pub warning_label: *mut gtk::ffi::GtkWidget, + pub info_box: *mut gtk::ffi::GtkWidget, + pub time_box: *mut gtk::ffi::GtkWidget, + pub clock_label: *mut gtk::ffi::GtkWidget, + pub date_label: *mut gtk::ffi::GtkWidget, + pub module_data: __IncompleteArrayField<*mut c_void>, +} + +#[repr(C)] +pub struct GtkLock { + pub windows: *mut glib::ffi::GArray, +} + +// Safe wrapper for Window pointer operations +pub struct WindowContext(*mut Window); + +impl WindowContext { + /// # Safety + /// `ctx` must be a valid pointer to a `Window` struct. + pub unsafe fn new(ctx: *mut Window) -> Option { + if ctx.is_null() { + None + } else { + Some(Self(ctx)) + } + } + + pub unsafe fn get_error_label(&self) -> gtk::Label { + from_glib_none((*self.0).error_label as *mut gtk::ffi::GtkLabel) + } + + pub unsafe fn get_overlay(&self) -> gtk::Overlay { + from_glib_none((*self.0).overlay as *mut gtk::ffi::GtkOverlay) + } + + pub unsafe fn get_monitor(&self) -> Monitor { + from_glib_none((*self.0).monitor as *mut gdk::ffi::GdkMonitor) + } + + /// # Safety + /// The caller must ensure `module_data` has been initialized and points to a valid `WindowData`. + pub unsafe fn set_data(&self, data: Box) { + let raw_ptr = Box::into_raw(data); + (*(*self.0).module_data.as_ptr()) = raw_ptr as *mut c_void; + } + + pub unsafe fn get_data_ptr(&self) -> *mut WindowData { + *(*self.0).module_data.as_ptr() as *mut WindowData + } + + /// # Safety + /// The caller takes ownership of the data and must drop it. + pub unsafe fn take_data(&self) -> Option> { + let ptr_ref = (*self.0).module_data.as_ptr(); + let ptr = *ptr_ref; + if ptr.is_null() { + None + } else { + *ptr_ref = ptr::null_mut(); // Clear it + Some(Box::from_raw(ptr as *mut WindowData)) + } + } +} + diff --git a/src/handler.rs b/src/handler.rs new file mode 100644 index 0000000..fc1717d --- /dev/null +++ b/src/handler.rs @@ -0,0 +1,165 @@ +use gtk::prelude::*; +use gtk::{glib, gdk, gdk_pixbuf, gio}; +use gstreamer as gst; +use gstreamer_player as gst_player; +use rand::Rng; +use crate::state::{MODULE_STATE, WindowData}; +use crate::context::WindowContext; + +const SPRITE_MARGIN: i32 = 100; +const SPRITE_SCALE: f64 = 0.6; +const PLAYER_POOL_SIZE: usize = 3; +const RETRY_ATTEMPTS: usize = 10; + +pub struct WindowHandler; + +impl WindowHandler { + pub unsafe fn create(ctx: &WindowContext) { + println!("[ahfail] on_window_create called"); + + let error_label = ctx.get_error_label(); + let overlay = ctx.get_overlay(); + let monitor = ctx.get_monitor(); + + let geom = monitor.geometry(); + let screen_w = geom.width(); + let screen_h = geom.height(); + + let fixed = gtk::Fixed::new(); + fixed.set_size_request(screen_w, screen_h); + fixed.set_halign(gtk::Align::Fill); + fixed.set_valign(gtk::Align::Fill); + fixed.set_hexpand(true); + fixed.set_vexpand(true); + + overlay.add_overlay(&fixed); + overlay.set_overlay_pass_through(&fixed, true); + + let mut ready_players = Vec::new(); + MODULE_STATE.with(|state| { + if let Some(audio_uri) = &state.borrow().audio_uri { + for _ in 0..PLAYER_POOL_SIZE { + ready_players.push(Self::create_player(audio_uri)); + } + } + }); + + let data = Box::new(WindowData { + sprites: Vec::new(), + active_players: Vec::new(), + ready_players, + fixed: fixed.clone(), + signal_id: None, + }); + + ctx.set_data(data); + let ptr_addr = ctx.get_data_ptr() as usize; + + let signal_id = error_label.connect_notify(Some("label"), move |label, _| { + let text = label.text(); + let text_str = text.as_str(); + if text_str.is_empty() { + return; + } + println!("[ahfail] Error label changed to: '{}'", text_str); + + MODULE_STATE.with(|state| { + let state = state.borrow(); + if let (Some(animation), Some(audio_uri)) = (&state.animation, &state.audio_uri) { + let data = unsafe { &mut *(ptr_addr as *mut WindowData) }; + + let image = gtk::Image::from_animation(animation); + image.show(); + + let sprite_w = animation.width(); + let sprite_h = animation.height(); + + let mut rng = rand::thread_rng(); + + let safe_w = screen_w - SPRITE_MARGIN; + let safe_h = screen_h - SPRITE_MARGIN; + + let max_x = if safe_w > sprite_w { safe_w - sprite_w } else { 0 }; + let max_y = if safe_h > sprite_h { safe_h - sprite_h } else { 0 }; + + let mut x = 0; + let mut y = 0; + let mut found_safe_spot = false; + + for _ in 0..RETRY_ATTEMPTS { + x = rng.gen_range(0..=max_x); + y = rng.gen_range(0..=max_y); + + if let Some(deadzone) = &state.config.deadzone { + let sprite_rect = gdk::Rectangle::new(x, y, sprite_w, sprite_h); + if deadzone.intersect(&sprite_rect).is_none() { + found_safe_spot = true; + break; + } + } else { + found_safe_spot = true; + break; + } + } + + if !found_safe_spot { + println!("[ahfail] Could not find safe spot after retries, placing anyway"); + } + + println!("[ahfail] Placing sprite at ({}, {})", x, y); + data.fixed.put(&image, x, y); + data.sprites.push(image); + + let player = if let Some(p) = data.ready_players.pop() { + p + } else { + Self::create_player(audio_uri) + }; + + player.play(); + data.active_players.push(player); + + let new_player = Self::create_player(audio_uri); + data.ready_players.push(new_player); + } + }); + }); + + unsafe { + (*ctx.get_data_ptr()).signal_id = Some(signal_id); + } + } + + pub unsafe fn destroy(ctx: &WindowContext) { + if let Some(data) = ctx.take_data() { + if let Some(signal_id) = data.signal_id { + let error_label = ctx.get_error_label(); + error_label.disconnect(signal_id); + } + + for sprite in data.sprites { + sprite.destroy(); + } + for player in data.active_players { + player.stop(); + } + for player in data.ready_players { + player.stop(); + } + data.fixed.destroy(); + } + } + + fn create_player(uri: &str) -> gst_player::Player { + let player = gst_player::Player::new(None, None); + player.set_uri(Some(uri)); + player.connect_end_of_stream(glib::clone!(@weak player => move |_| { + player.seek(gst::ClockTime::from_seconds(0)); + })); + player.connect_error(|_, err| { + eprintln!("[ahfail] GStreamer Player Error: {}", err); + }); + player + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d18837d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,193 @@ +pub mod config; +pub mod context; +pub mod state; +pub mod handler; + +use gtk::prelude::*; +use gtk::{glib, gdk_pixbuf, gio}; +use gtk::gdk_pixbuf::InterpType; +use gstreamer as gst; +use std::ffi::{c_void, CStr}; +use glib::translate::from_glib_none; +use std::os::raw::{c_char, c_int, c_uint}; +use std::ptr; + +// Re-export types for external use (e.g. integration tests) +pub use config::{ModuleConfig, DEADZONE_ARG, DEADZONE_LONG, DEADZONE_DESC, DEADZONE_ARG_DESC}; +pub use context::{Window, GtkLock, WindowContext, __IncompleteArrayField}; +pub use state::{MODULE_STATE, WindowData, ModuleState}; +pub use handler::WindowHandler; + +// Scale factor to reduce the image size and avoid cropping/overlap +const SPRITE_SCALE: f64 = 0.6; + +#[no_mangle] +pub static module_name: [c_char; 7] = [ + b'a' as c_char, + b'h' as c_char, + b'f' as c_char, + b'a' as c_char, + b'i' as c_char, + b'l' as c_char, + 0, +]; + +#[no_mangle] +pub static module_major_version: c_uint = 4; + +#[no_mangle] +pub static module_minor_version: c_uint = 0; + +#[no_mangle] +pub static mut module_entries: [glib::ffi::GOptionEntry; 3] = [ + glib::ffi::GOptionEntry { + long_name: DEADZONE_LONG.as_ptr() as *const c_char, + short_name: 0, + flags: 0, + arg: glib::ffi::G_OPTION_ARG_STRING, + arg_data: unsafe { &raw mut DEADZONE_ARG as *mut _ }, + description: DEADZONE_DESC.as_ptr() as *const c_char, + arg_description: DEADZONE_ARG_DESC.as_ptr() as *const c_char + }, + glib::ffi::GOptionEntry { long_name: ptr::null(), short_name: 0, flags: 0, arg: 0, arg_data: ptr::null_mut(), description: ptr::null(), arg_description: ptr::null() }, + glib::ffi::GOptionEntry { long_name: ptr::null(), short_name: 0, flags: 0, arg: 0, arg_data: ptr::null_mut(), description: ptr::null(), arg_description: ptr::null() }, +]; + +extern "C" { + fn ahfail_get_resource() -> *mut gio::ffi::GResource; +} + +// Helper for tests to inspect window data +/// # Safety +/// `ctx` must be a valid Window pointer. +pub unsafe fn get_window_data_ref(ctx: *mut Window) -> Option<&'static state::WindowData> { + let ptr = *(*ctx).module_data.as_ptr(); + if ptr.is_null() { + None + } else { + Some(&*(ptr as *mut state::WindowData)) + } +} + +/// # Safety +/// This function is called by the C host. +#[no_mangle] +pub unsafe extern "C" fn on_activation(_gtklock: *mut GtkLock, _id: c_int) { + println!("[ahfail] on_activation called"); + if let Err(e) = gtk::init() { + eprintln!("Failed to initialize GTK bindings: {}", e); + return; + } + if let Err(e) = gst::init() { + eprintln!("Failed to initialize GStreamer: {}", e); + return; + } + + let resource_ptr = ahfail_get_resource(); + if !resource_ptr.is_null() { + let resource = from_glib_none::<_, gio::Resource>(resource_ptr); + gio::resources_register(&resource); + } + + // Load frames + let mut loaded_frames: Vec = Vec::new(); + match gio::resources_enumerate_children("/ahfail/sprites", gio::ResourceLookupFlags::NONE) { + Ok(mut frames) => { + frames.sort(); + for frame_path in frames { + let full_path = format!("/ahfail/sprites/{}", frame_path); + match gdk_pixbuf::Pixbuf::from_resource(&full_path) { + Ok(pixbuf) => { + let w = (pixbuf.width() as f64 * SPRITE_SCALE) as i32; + let h = (pixbuf.height() as f64 * SPRITE_SCALE) as i32; + if let Some(scaled) = pixbuf.scale_simple(w, h, InterpType::Bilinear) { + loaded_frames.push(scaled); + } else { + loaded_frames.push(pixbuf); + } + }, + Err(e) => eprintln!("Failed to load sprite frame {}: {}", full_path, e), + } + } + }, + Err(e) => eprintln!("Failed to enumerate sprites: {}", e), + } + + let anim_opt = if !loaded_frames.is_empty() { + let first = &loaded_frames[0]; + let anim = gdk_pixbuf::PixbufSimpleAnim::new(first.width(), first.height(), 12.0); + anim.set_loop(true); + for frame in loaded_frames { + anim.add_frame(&frame); + } + Some(anim.upcast()) + } else { + None + }; + + let config = ModuleConfig::from_args(); + + MODULE_STATE.with(|state| { + let mut state = state.borrow_mut(); + state.animation = anim_opt; + state.audio_uri = Some("resource:///ahfail/audio/magic-word.mp3".to_string()); + state.config = config; + }); +} + +/// # Safety +/// This function is called by the C host. +#[no_mangle] +pub unsafe extern "C" fn on_window_create(_gtklock: *mut GtkLock, ctx: *mut Window) { + if let Some(win_ctx) = WindowContext::new(ctx) { + WindowHandler::create(&win_ctx); + } +} + +/// # Safety +/// This function is called by the C host. +#[no_mangle] +pub unsafe extern "C" fn on_window_destroy(_gtklock: *mut GtkLock, ctx: *mut Window) { + if let Some(win_ctx) = WindowContext::new(ctx) { + WindowHandler::destroy(&win_ctx); + } +} + +/// # Safety +/// This function is called by the C host. +#[no_mangle] +pub unsafe extern "C" fn on_idle_hide(gtklock: *mut GtkLock) { + if gtklock.is_null() { return; } + let garray = (*gtklock).windows; + if !garray.is_null() { + let len = (*garray).len; + let data_ptr = (*garray).data as *mut *mut Window; + let windows = std::slice::from_raw_parts(data_ptr, len as usize); + + for &window_ptr in windows { + on_window_destroy(ptr::null_mut(), window_ptr); + } + } +} + +/// # Safety +/// This function is called by the C host. +#[no_mangle] +pub unsafe extern "C" fn on_focus_change(_gtklock: *mut GtkLock, _win: *mut Window, _old: *mut Window) {} + +/// # Safety +/// This function is called by the C host. +#[no_mangle] +pub unsafe extern "C" fn on_idle_show(_gtklock: *mut GtkLock) {} + +/// # Safety +/// This function is called by the C host. +#[no_mangle] +pub unsafe extern "C" fn g_module_unload(_module: *mut c_void) { + MODULE_STATE.with(|state| { + let mut state = state.borrow_mut(); + state.animation = None; + state.audio_uri = None; + state.config.deadzone = None; + }); +} \ No newline at end of file diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..1367b21 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,26 @@ +use gtk::{glib, gdk_pixbuf}; +use gstreamer_player as gst_player; +use std::cell::RefCell; +use crate::config::ModuleConfig; + +pub struct ModuleState { + pub animation: Option, + pub audio_uri: Option, + pub config: ModuleConfig, +} + +pub struct WindowData { + pub sprites: Vec, + pub active_players: Vec, + pub ready_players: Vec, + pub fixed: gtk::Fixed, + pub signal_id: Option, +} + +thread_local! { + pub static MODULE_STATE: RefCell = const { RefCell::new(ModuleState { + animation: None, + audio_uri: None, + config: ModuleConfig { deadzone: None }, + }) }; +} diff --git a/src/utils/bench.rs b/src/utils/bench.rs new file mode 100644 index 0000000..99f7fc8 --- /dev/null +++ b/src/utils/bench.rs @@ -0,0 +1,12 @@ +use std::time::Instant; + +pub fn time_execution(name: &str, f: F) -> T +where + F: FnOnce() -> T, +{ + let start = Instant::now(); + let result = f(); + let duration = start.elapsed(); + println!("[benchmark] {}: {:?}", name, duration); + result +} diff --git a/tests/ahfail_tests.rs b/tests/ahfail_tests.rs new file mode 100644 index 0000000..26106df --- /dev/null +++ b/tests/ahfail_tests.rs @@ -0,0 +1,299 @@ +use ahfail_module::*; +use gtk::prelude::*; +use gtk::{glib, gdk_pixbuf, gdk}; +use gstreamer as gst; +use std::ptr; +use std::ffi::c_void; +use gtk::glib::translate::{ToGlibPtr, Stash}; + +// Mock implementation of the resource getter for tests +#[no_mangle] +extern "C" fn ahfail_get_resource() -> *mut gtk::gio::ffi::GResource { + ptr::null_mut() +} + +// Helper struct to simulate the C flexible array member allocation +#[repr(C)] +struct WindowWithStorage { + window: Window, + // Reserve space for the 'module_data' flexible array (1 pointer) + storage: *mut c_void, +} + +fn setup_test_environment() { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + let _ = gtk::init(); + let _ = gst::init(); + }); +} + +fn flush_events() { + while gtk::events_pending() { + gtk::main_iteration(); + } +} + +fn create_mock_context() -> (WindowWithStorage, gtk::Window, gtk::Label, gtk::Overlay, gdk::Display) { + setup_test_environment(); + + let error_label = gtk::Label::new(Some("")); + let overlay = gtk::Overlay::new(); + let window_widget = gtk::Window::new(gtk::WindowType::Toplevel); + let display = gdk::Display::default().expect("No display available for testing"); + let monitor = display.monitor(0).expect("No monitor available"); + + // Explicitly type the Stash to avoid ambiguity + let monitor_stash: Stash<*mut gdk::ffi::GdkMonitor, _> = monitor.to_glib_none(); + let window_stash: Stash<*mut gtk::ffi::GtkWindow, _> = window_widget.to_glib_none(); + let overlay_stash: Stash<*mut gtk::ffi::GtkOverlay, _> = overlay.to_glib_none(); + let label_stash: Stash<*mut gtk::ffi::GtkLabel, _> = error_label.to_glib_none(); + + let win = Window { + monitor: monitor_stash.0, + window: window_stash.0 as *mut gtk::ffi::GtkWidget, + overlay: overlay_stash.0, + window_box: ptr::null_mut(), + body_revealer: ptr::null_mut(), + body_grid: ptr::null_mut(), + input_label: ptr::null_mut(), + input_field: ptr::null_mut(), + message_revealer: ptr::null_mut(), + message_scrolled_window: ptr::null_mut(), + message_box: ptr::null_mut(), + unlock_button: ptr::null_mut(), + error_label: label_stash.0 as *mut gtk::ffi::GtkWidget, + warning_label: ptr::null_mut(), + info_box: ptr::null_mut(), + time_box: ptr::null_mut(), + clock_label: ptr::null_mut(), + date_label: ptr::null_mut(), + module_data: __IncompleteArrayField::new(), + }; + + let storage = WindowWithStorage { + window: win, + storage: ptr::null_mut(), + }; + + (storage, window_widget, error_label, overlay, display) +} + +fn inject_test_state() { + let pixbuf = gdk_pixbuf::Pixbuf::new(gdk_pixbuf::Colorspace::Rgb, false, 8, 1, 1).unwrap(); + let anim = gdk_pixbuf::PixbufSimpleAnim::new(1, 1, 1.0); + anim.add_frame(&pixbuf); + + // Use a minimal valid WAV (silent) to avoid GStreamer errors + let wav_base64 = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQEAAAAA"; + let data_uri = format!("data:audio/wav;base64,{}", wav_base64); + + MODULE_STATE.with(|state| { + let mut state = state.borrow_mut(); + state.animation = Some(anim.upcast()); + state.audio_uri = Some(data_uri); + }); +} + +fn run_test_01_initialization() { + unsafe { + on_activation(ptr::null_mut(), 0); + } + MODULE_STATE.with(|state| { + let state = state.borrow(); + // It should have set the audio URI to the default + assert_eq!(state.audio_uri.as_deref(), Some("resource:///ahfail/audio/magic-word.mp3")); + }); +} + +fn run_test_02_window_create_null() { + unsafe { + on_window_create(ptr::null_mut(), ptr::null_mut()); + } +} + +fn run_test_03_window_create_valid() { + let (mut win_storage, _w, _l, _o, _) = create_mock_context(); + let ctx_ptr = &mut win_storage.window as *mut Window; + + unsafe { + on_window_create(ptr::null_mut(), ctx_ptr); + let data = get_window_data_ref(ctx_ptr); + assert!(data.is_some(), "WindowData should be initialized"); + + on_window_destroy(ptr::null_mut(), ctx_ptr); + flush_events(); + } +} + +fn run_test_04_trigger_effect_sprite() { + let (mut win_storage, _w, label, _o, _) = create_mock_context(); + let ctx_ptr = &mut win_storage.window as *mut Window; + inject_test_state(); + + unsafe { + on_window_create(ptr::null_mut(), ctx_ptr); + label.set_text("Error"); + flush_events(); + + let data = get_window_data_ref(ctx_ptr).unwrap(); + assert_eq!(data.sprites.len(), 1); + + on_window_destroy(ptr::null_mut(), ctx_ptr); + flush_events(); + } +} + +fn run_test_05_trigger_effect_audio() { + let (mut win_storage, _w, label, _o, _) = create_mock_context(); + let ctx_ptr = &mut win_storage.window as *mut Window; + inject_test_state(); + + unsafe { + on_window_create(ptr::null_mut(), ctx_ptr); + label.set_text("Error"); + flush_events(); + + let data = get_window_data_ref(ctx_ptr).unwrap(); + assert_eq!(data.active_players.len(), 1); + + on_window_destroy(ptr::null_mut(), ctx_ptr); + flush_events(); + } +} + +fn run_test_06_multiple_triggers() { + let (mut win_storage, _w, label, _o, _) = create_mock_context(); + let ctx_ptr = &mut win_storage.window as *mut Window; + inject_test_state(); + + unsafe { + on_window_create(ptr::null_mut(), ctx_ptr); + for _ in 0..5 { + label.set_text("Error"); + flush_events(); + } + + let data = get_window_data_ref(ctx_ptr).unwrap(); + assert_eq!(data.sprites.len(), 5); + assert_eq!(data.active_players.len(), 5); + + on_window_destroy(ptr::null_mut(), ctx_ptr); + flush_events(); + } +} + +fn run_test_07_window_cleanup() { + let (mut win_storage, _w, label, _o, _) = create_mock_context(); + let ctx_ptr = &mut win_storage.window as *mut Window; + inject_test_state(); + + unsafe { + on_window_create(ptr::null_mut(), ctx_ptr); + label.set_text("Error"); + flush_events(); + + assert!(get_window_data_ref(ctx_ptr).is_some()); + on_window_destroy(ptr::null_mut(), ctx_ptr); + assert!(get_window_data_ref(ctx_ptr).is_none()); + flush_events(); + } +} + +fn run_test_08_multiple_windows() { + let (mut win1, _w1, label1, _o1, _) = create_mock_context(); + let (mut win2, _w2, label2, _o2, _) = create_mock_context(); + let ctx1 = &mut win1.window as *mut Window; + let ctx2 = &mut win2.window as *mut Window; + inject_test_state(); + + unsafe { + on_window_create(ptr::null_mut(), ctx1); + on_window_create(ptr::null_mut(), ctx2); + + label1.set_text("E1"); + flush_events(); + + label2.set_text("E2"); + label2.set_text("E2 again"); + flush_events(); + + let data1 = get_window_data_ref(ctx1).unwrap(); + let data2 = get_window_data_ref(ctx2).unwrap(); + + assert_eq!(data1.sprites.len(), 1); + assert_eq!(data2.sprites.len(), 2); + + on_window_destroy(ptr::null_mut(), ctx1); + on_window_destroy(ptr::null_mut(), ctx2); + flush_events(); + } +} + +fn run_test_09_idle_hide_cleanup() { + let (mut win_storage, _w, label, _o, _) = create_mock_context(); + let ctx_ptr = &mut win_storage.window as *mut Window; + inject_test_state(); + + unsafe { + on_window_create(ptr::null_mut(), ctx_ptr); + label.set_text("Error"); + flush_events(); + + // Mock GtkLock struct + let mut windows_array = glib::ffi::g_array_new(0, 0, std::mem::size_of::<*mut Window>() as u32); + glib::ffi::g_array_append_vals(windows_array, &ctx_ptr as *const _ as *const c_void, 1); + + let mut lock = GtkLock { + windows: windows_array, + }; + + on_idle_hide(&mut lock); + + assert!(get_window_data_ref(ctx_ptr).is_none()); + glib::ffi::g_array_free(windows_array, 1); + flush_events(); + } +} + +fn run_test_10_module_unload() { + inject_test_state(); + MODULE_STATE.with(|state| { + assert!(state.borrow().audio_uri.is_some()); + }); + unsafe { + g_module_unload(ptr::null_mut()); + } + MODULE_STATE.with(|state| { + let state = state.borrow(); + assert!(state.animation.is_none()); + assert!(state.audio_uri.is_none()); + }); +} + +#[test] +fn run_all_tests_sequentially() { + println!("Running test 01..."); + run_test_01_initialization(); + println!("Running test 02..."); + run_test_02_window_create_null(); + println!("Running test 03..."); + run_test_03_window_create_valid(); + println!("Running test 04..."); + run_test_04_trigger_effect_sprite(); + println!("Running test 05..."); + run_test_05_trigger_effect_audio(); + println!("Running test 06..."); + run_test_06_multiple_triggers(); + println!("Running test 07..."); + run_test_07_window_cleanup(); + println!("Running test 08..."); + run_test_08_multiple_windows(); + println!("Running test 09..."); + run_test_09_idle_hide_cleanup(); + println!("Running test 10..."); + run_test_10_module_unload(); + + // Final flush + flush_events(); +} \ No newline at end of file diff --git a/tests/benchmarks.rs b/tests/benchmarks.rs new file mode 100644 index 0000000..fa86cd1 --- /dev/null +++ b/tests/benchmarks.rs @@ -0,0 +1,57 @@ +use gtk::prelude::*; +use gtk::{gdk, gdk_pixbuf}; +use gstreamer as gst; +use gstreamer_player as gst_player; +use std::time::Instant; + +#[cfg(test)] +mod benchmarks { + use super::*; + use std::sync::Once; + + static INIT: Once = Once::new(); + + fn init() { + INIT.call_once(|| { + gtk::init().unwrap(); + gst::init().unwrap(); + }); + } + + #[test] + fn bench_pixbuf_loading() { + init(); + let start = Instant::now(); + // Create a 1x1 pixbuf to simulate loading + let _pixbuf = gdk_pixbuf::Pixbuf::new(gdk_pixbuf::Colorspace::Rgb, false, 8, 220, 220).unwrap(); + println!("Pixbuf creation: {:?}", start.elapsed()); + } + + #[test] + fn bench_animation_creation() { + init(); + let start = Instant::now(); + let _anim = gdk_pixbuf::PixbufSimpleAnim::new(220, 220, 12.0); + println!("Animation creation: {:?}", start.elapsed()); + } + + #[test] + fn bench_player_creation() { + init(); + let start = Instant::now(); + let _player = gst_player::Player::new(None, None); + println!("GstPlayer creation: {:?}", start.elapsed()); + } + + #[test] + fn bench_coord_calculation() { + use rand::Rng; + let start = Instant::now(); + let mut rng = rand::thread_rng(); + for _ in 0..1000 { + let _x = rng.gen_range(0..1920); + let _y = rng.gen_range(0..1080); + } + println!("1000 RNG calculations: {:?}", start.elapsed()); + } +} diff --git a/tests/module_test.c b/tests/module_test.c new file mode 100644 index 0000000..2de46ed --- /dev/null +++ b/tests/module_test.c @@ -0,0 +1,18 @@ +#include + +#include "ahfail/module.h" + +int main(void) { + if (module_name[0] == '\0') { + return 1; + } + + on_activation(NULL, 42); + on_window_create(NULL, NULL); + on_focus_change(NULL, NULL, NULL); + on_idle_show(NULL); + on_idle_hide(NULL); + on_window_destroy(NULL, NULL); + + return 0; +}