Compare commits

...

328 commits
0.1 ... main

Author SHA1 Message Date
DashieTM f229ed7dea Update nix packages
Some checks failed
Rust-build / Build (push) Has been cancelled
2025-02-15 16:17:06 +01:00
DashieTM f09ed75581 Use nix CI
Some checks failed
Rust-build / Build (push) Has been cancelled
2024-12-03 23:31:22 +01:00
DashieTM 6c1291bd19 debian: fix recommendations and dependencies 2024-06-13 15:14:29 +02:00
DashieTM a369356b49 chore: Bump version 2024-06-13 14:38:16 +02:00
DashieTM ee7129009a chore: Update todos 2024-06-12 23:52:09 +02:00
DashieTM e7c0995aa3 init: store true if daemon is already started 2024-06-12 19:46:11 +02:00
DashieTM 0b12d95c83 readme: Add plugin confirmation 2024-06-12 17:21:02 +02:00
DashieTM e4971391be readme: Add installation guide mention 2024-06-12 10:50:24 +02:00
DashieTM d36e79614c readme: Add exact binary names 2024-06-12 10:38:48 +02:00
DashieTM 7532c1e065 chore: Bump version in all files 2024-06-11 20:32:22 +02:00
DashieTM a9ee1b7bc3 flatpak: Change name to ReSet case 2024-06-11 15:09:34 +02:00
takotori 77c53a3efc change active language color 2024-06-11 13:09:40 +02:00
DashieTM 2b0898b809 CI/CD: update dependencies on distros 2024-06-11 12:49:07 +02:00
DashieTM 69d131282c chore: Bump version and deps 2024-06-10 16:22:51 +02:00
DashieTM 5e8241bf11 flatpak: Change name to ReSet case 2024-06-10 16:19:39 +02:00
DashieTM 390099fbc0 nix: use new name 2024-06-07 02:29:23 +02:00
DashieTM 778952d887 flatpak: rename reset 2024-06-07 02:13:15 +02:00
DashieTM 9fde38ca2d chore: rename ReSet.desktop 2024-06-07 01:48:05 +02:00
DashieTM 563aa16f80 pkgbuild: fixup names 2024-06-07 01:39:45 +02:00
DashieTM 35dfec596d chore: Bump version in packages 2024-06-07 01:33:10 +02:00
DashieTM 5f1fad8154 chore: Bump version 2024-06-07 01:32:37 +02:00
DashieTM ab266dddb6 chore: Rename only bin to ReSet 2024-06-07 01:31:05 +02:00
DashieTM 56f77f767f chore: update cargo.lock 2024-06-07 00:39:32 +02:00
DashieTM 1a1753f551 chore: chagne name of program 2024-06-07 00:38:11 +02:00
DashieTM 51d01cadba readme: update to new name 2024-06-07 00:32:58 +02:00
DashieTM d75c9aa78d chore: Bump version 2024-06-07 00:30:18 +02:00
DashieTM b7151f1feb chore: change name to ReSet to avoid conflicts 2024-06-07 00:28:40 +02:00
DashieTM 8ddaea9a7d CI/CD: move lib to bin 2024-06-06 23:21:31 +02:00
DashieTM 12bd13f3e1 CI/CD: generate debian structure 2024-06-06 22:37:22 +02:00
DashieTM 452eac3209 deps: Bump version 2024-06-06 22:24:10 +02:00
DashieTM c297717ca6 deb: update includes 2024-06-06 21:54:45 +02:00
DashieTM cc3e6647b4 pkgbuild: bump version 2024-06-06 20:37:50 +02:00
DashieTM 8ddd91bf93 chore: update cargo.lock 2024-06-06 19:07:45 +02:00
DashieTM 7c2a4e7934 chore: Bump version 2024-06-06 19:04:12 +02:00
DashieTM e384a4e5e4 CI/CD: add postinstall for debian 2024-06-06 19:03:49 +02:00
DashieTM b76a718f52 readme: add plugins 2024-06-06 18:55:38 +02:00
DashieTM 56155d29b3 readme: add nix 2024-06-06 18:52:34 +02:00
DashieTM e839a2572f nix: use recursive update to merge toml 2024-06-06 15:41:35 +02:00
DashieTM 26ccb90688 nix: use mkMerge as function 2024-06-06 15:40:08 +02:00
DashieTM a50f28dbe6 nix: merge attr sets 2024-06-06 15:39:21 +02:00
DashieTM 404198be3d chore: update cargo lock 2024-06-06 15:37:15 +02:00
DashieTM 9832fc3ea9 deps: use latest daemon 2024-06-06 15:36:16 +02:00
DashieTM d88affa6c8 nix: change toml additions 2024-06-06 15:33:03 +02:00
DashieTM dcc9de9230 startup: Add spinloop until deamon is ready 2024-06-05 00:43:23 +02:00
DashieTM 918bf9c70a startup: Add spinloop until daemon is ready 2024-06-04 23:37:15 +02:00
DashieTM 6a605f1d08 fix: move to 10 seconds for the daemon to start 2024-06-03 22:15:07 +02:00
DashieTM f1ee049e17 nix: improve plugin installation 2024-06-03 14:30:54 +02:00
takotori 93131a185a
Merge pull request #102 from Xetibo/ina
add frontend tests
2024-06-01 14:28:36 +02:00
takotori 1e4ca9f73a add frontend tests 2024-06-01 14:26:28 +02:00
DashieTM fe85ff3c70 chore: Bump version 2024-05-31 18:48:12 +02:00
DashieTM d49523f69e deps: Bump version of daemon 2024-05-31 18:47:56 +02:00
DashieTM 4220726ea8 flatpak: add debug version 2024-05-31 18:38:44 +02:00
DashieTM c9d3ac9317 nix: Allow definition of further toml values 2024-05-31 14:24:54 +02:00
DashieTM 073e88ab4f nix: Allow definition of further toml values 2024-05-31 14:22:19 +02:00
DashieTM 40beca475f nix: rename plugins with underscore 2024-05-30 21:27:35 +02:00
DashieTM 14cfcc16b0 nix: rename plugins 2024-05-30 21:24:57 +02:00
DashieTM 84bfad134a packages: bump version 2024-05-30 21:22:41 +02:00
DashieTM 7f8b9bd101 packages: bump version 2024-05-30 21:21:21 +02:00
DashieTM 1df2c3a369 nix: add plugin_path 2024-05-30 21:03:46 +02:00
DashieTM 3a31a4ce3b nix: test without toml values 2024-05-30 19:40:43 +02:00
DashieTM 9f34ffeb9c nix: fix toml conversion 2024-05-30 19:39:36 +02:00
DashieTM f729310502 nix: fix toml conversion 2024-05-30 19:38:55 +02:00
DashieTM f5d0eae840 nix: add plugin entry 2024-05-30 19:30:01 +02:00
takotori ecfaf2badd
Merge pull request #101 from Xetibo/ina2
add color to keyboard layouts
2024-05-26 13:24:39 +02:00
takotori a8d2e4c2ce add color to keyboard layouts 2024-05-26 13:24:16 +02:00
DashieTM 3cb7cd2d7f chore: rustup nightly breaks nixos with ldd -> rollback version of rust 2024-05-20 22:07:35 +02:00
DashieTM 397c48ae43 nix: use flake 2024-05-20 21:23:58 +02:00
DashieTM 84660083f9 chore: bump versions of dependencies, small refactor on errors -> use macros 2024-05-06 20:47:26 +02:00
DashieTM 822a6a7146 chore: Bump Cargo.lock 2024-04-24 11:59:47 +02:00
DashieTM 4a93fac141 chore: Bump version 2024-04-24 11:55:42 +02:00
DashieTM ef5d4c99d6 chore: Bump cargo.lock 2024-04-24 11:42:45 +02:00
DashieTM 3c85f39e56 chore: Bump version 2024-04-24 11:38:23 +02:00
DashieTM 48fbb103d5 chore: Readd cargo.lock 2024-04-24 11:26:54 +02:00
DashieTM 39c338246f chore: Change nix depencendies 2024-04-20 10:14:14 +02:00
DashieTM ebd4bba4ea chore: Add nix config 2024-04-15 18:21:38 +02:00
takotori ee31829897
Merge pull request #100 from Xetibo/dashie
feat: Dynamic sidebars
2024-04-11 09:55:11 +02:00
Dashie 232481417e
Merge branch 'main' into dashie 2024-04-11 09:49:31 +02:00
Fabio Lenherr / DashieTM 65f8bd80be feat: Add conditional backend check for plugins 2024-04-09 21:51:38 +02:00
Fabio Lenherr / DashieTM ebbd226676 feat: Add dynamic checks for supported plugins 2024-04-09 17:34:48 +02:00
takotori a7489f7dbb
adw needs to be initialized in frontend startup before frontend data (#99) 2024-04-07 11:00:34 +02:00
Fabio Lenherr / DashieTM 5c7bdd6889 wip: Attempt to solve audio bug 2024-04-05 22:07:37 +02:00
Fabio Lenherr / DashieTM f8f23faece feat: Add conditional features based on daemon capabilities 2024-04-05 12:54:05 +02:00
Fabio Lenherr / DashieTM 9dbf4228fc chore: Remove unused import 2024-04-04 19:12:46 +02:00
Fabio Lenherr / DashieTM 12d5784f88 fix: Wifi connect and disconnect 2024-04-04 19:11:43 +02:00
dashie d02da7d3f7 wip: fix bluetooth and wifi 2024-04-04 18:10:12 +02:00
dashie a8aca57ea1 feat: Add ability to show bluetooth devices immediately 2024-04-04 10:54:20 +02:00
Dashie 5f0781ee18
refactor: generic audio implementation (#98)
* refactor: Combine new_plugin and new for sidebar entries

* chore: Format project

* chore: Fix clippy warnings

* wip: generic audio implementation

* wip: format and fix warnings

* wip: Refactor Dbus functions

* wip: Add TAudioStreamObject

* fix: use prelude instead of traits

* wip: Add generic audio stream

* wip: Work on generic audio listener

* wip: Finish audio generics refactoring

* fix: Use proper path for dbus function

* chore: Format project

* fix: Use adw prelude instead of traits
2024-04-04 08:00:16 +02:00
takotori fd99d902c2
Merge pull request #97 from Xetibo/dashie
refactor: SideBar and PluginSideBar
2024-04-01 19:01:50 +02:00
Fabio Lenherr / DashieTM f2e833eae5 chore: Fix clippy warnings 2024-04-01 14:49:59 +02:00
Fabio Lenherr / DashieTM d72f916e8a chore: Format project 2024-04-01 13:40:21 +02:00
Fabio Lenherr / DashieTM 3d2f38ee46 refactor: Combine new_plugin and new for sidebar entries 2024-04-01 13:39:55 +02:00
Dashie 12c937ae39
Merge pull request #96 from Xetibo/dashie
chore: Change license to GPL3-or-later
2024-03-29 16:55:56 +01:00
Fabio Lenherr / DashieTM d5392dfc87 chore: Change license to GPL3-or-later 2024-03-29 16:55:26 +01:00
Dashie 0268f31733
Merge pull request #91 from Xetibo/ina
feat: refactoring, plugin base, example plugin
2024-03-29 16:52:36 +01:00
takotori 00ed3f1666 add : 2024-03-29 16:51:32 +01:00
takotori 83a722507a add refactoring todo 2024-03-29 16:46:41 +01:00
takotori a9e0758435 add example plugin
should be working now
add plugins to sidebar
add plugin system base
Create function signatures for plugins
Update crates
2024-03-29 16:44:53 +01:00
Fabio Lenherr / DashieTM 59fe7fc454 chore: Format project 2024-03-12 11:23:52 +01:00
Fabio Lenherr / DashieTM 239a0b62e9 refactor: Move audio to separate component as one 2024-03-12 11:21:24 +01:00
Fabio Lenherr / DashieTM ccf32a7d58 chore: Fix spelling mistake 2024-03-12 11:17:50 +01:00
Fabio Lenherr / DashieTM bca769b180 fix: Use proper function name in base 2024-03-12 11:15:58 +01:00
Fabio Lenherr / DashieTM eccd5c6093 chore: Use uniform naming for audio boxes 2024-03-12 11:14:49 +01:00
Fabio Lenherr / DashieTM fae04a811f refactor: further split audio logic 2024-03-12 11:09:12 +01:00
Fabio Lenherr / DashieTM ed435d6347 chore: Fix all warnings 2024-03-11 20:24:24 +01:00
Fabio Lenherr / DashieTM e62559a966 chore: move bluetooth handlers to separate file 2024-03-11 19:57:31 +01:00
Fabio Lenherr / DashieTM 0fe99fa3a6 chore: refactor wifi and bluetooth event handlers 2024-03-11 19:54:29 +01:00
Fabio Lenherr / DashieTM 0105956815 chore: refactor wifi and bluetooth event handlers 2024-03-11 19:41:03 +01:00
Fabio Lenherr / DashieTM 75050c840a feat: Implement error popup for bluetooth and network 2024-03-11 18:56:53 +01:00
Fabio Lenherr / DashieTM 16a30a7bdd feat: Add Error popup 2024-03-11 17:19:38 +01:00
Fabio Lenherr / DashieTM cc73c425f7 refactor: audio sources 2024-03-11 13:39:30 +01:00
DashieTM 260077006e chore: Remove accidental file 2024-03-10 22:09:27 +01:00
DashieTM ed6983830c refactor: sinkbox 2024-03-10 22:08:50 +01:00
Fabio Lenherr / DashieTM a510147d19 chore: Bump version of daemon 2023-12-21 13:14:24 +01:00
Fabio Lenherr / DashieTM e5263b0dc7 chore: Bump version 2023-12-21 13:10:11 +01:00
Fabio Lenherr / DashieTM 8796abb08f fix: Properly handle wifi and bluetooth turned on and off 2023-12-19 21:38:35 +01:00
dashie e1be42a55d chore: Bump version of PKGBUILD 2023-12-19 12:28:25 +00:00
dashie be3ff85800 chore: Bump version 2023-12-19 12:27:14 +00:00
dashie b34264027a chore: Bump version of Daemon 2023-12-19 11:51:38 +00:00
Fabio Lenherr / DashieTM 834206f819 chore: Bump version 2023-12-18 20:35:39 +01:00
Fabio Lenherr / DashieTM f4a8693436 fix: Reset activated state for first click 2023-12-18 20:34:07 +01:00
Fabio Lenherr / DashieTM 27e003e48a chore: Bump version of daemon and lib 2023-12-18 19:42:17 +01:00
Fabio Lenherr / DashieTM 24d3f5a49d chore: Add daemon entry 2023-12-18 16:33:03 +01:00
Fabio Lenherr / DashieTM f117345740 chore: Try different styling 2023-12-18 16:21:40 +01:00
Fabio Lenherr / DashieTM a868c5ff5b chore: Use div instead of center 2023-12-18 16:19:11 +01:00
Fabio Lenherr / DashieTM f8b91a20a0 chore: Remove unused files 2023-12-18 16:15:43 +01:00
Fabio Lenherr / DashieTM 3f69a18894 chore: Add proper Readme 2023-12-18 16:15:20 +01:00
Fabio Lenherr / DashieTM bb448eacf3 chore: Bump version of PKGBUILD 2023-12-17 22:43:30 +01:00
Fabio Lenherr / DashieTM 9c85bf6705 fix: Show selected tab at startup 2023-12-17 22:28:33 +01:00
Fabio Lenherr / DashieTM e0a672935e fix: Use splice instead of remove to remove udef behavior 2023-12-17 21:48:25 +01:00
Fabio Lenherr / DashieTM c3fa24ddc8 chore: Bump version 2023-12-17 18:21:21 +01:00
Fabio Lenherr / DashieTM 1c0b467d8a chore: Bump version of daemon 2023-12-17 18:21:02 +01:00
takotori 6055856f18 bump daemon 2023-12-16 19:49:14 +01:00
dashie 383d4bf61d fix: Use break instead of return in audio boxes 2023-12-16 16:15:32 +00:00
Fabio Lenherr / DashieTM 02fb844160 chore: Remove unused file 2023-12-16 12:18:29 +01:00
Fabio Lenherr / DashieTM c860fcddee fix: Use ssid over path for events 2023-12-16 12:18:13 +01:00
dashie 4a9f8bf90a chore: Format 2023-12-15 17:48:35 +00:00
dashie b97da674a8 chore: Open sidebar with ctrl-f 2023-12-15 17:48:10 +00:00
dashie 9d3fc438b0 chore: Bump version in arch build 2023-12-15 16:46:32 +00:00
dashie 27cf46dde4 chore: Bump version of daemon 2023-12-15 16:37:08 +00:00
dashie 9cddc33a75 chore: Bump version 2023-12-15 15:57:49 +00:00
dashie 37742471f4 fix: Don't lock UI when turning all audio devices off 2023-12-15 15:56:49 +00:00
Fabio Lenherr / DashieTM 7635c96d9f chore: Bump version of daemon 2023-12-14 22:07:57 +01:00
takotori f32301eebb
Merge pull request #84 from Xetibo/ina
fix margin
2023-12-14 17:08:09 +01:00
takotori 5564f8eafa fix margin 2023-12-14 17:07:32 +01:00
Fabio Lenherr / DashieTM bc08acda56 chore: Remove unused file 2023-12-14 16:38:31 +01:00
Fabio Lenherr / DashieTM c132765571 chore: Shrink CI/CD 2023-12-14 16:38:15 +01:00
Dashie ce15ac565f
Merge pull request #83 from Xetibo/ina
Ina
2023-12-14 15:35:21 +00:00
takotori 0595d8ff47 improve wifi ui 2023-12-14 16:34:13 +01:00
takotori db501b2e93 improve ui 2023-12-14 15:54:31 +01:00
Fabio Lenherr / DashieTM 834a462e4b ci: Fix flatpak json name 2023-12-13 14:18:26 +01:00
Fabio Lenherr / DashieTM 017381f776 chore: Bump version of daemon 2023-12-13 14:16:29 +01:00
Fabio Lenherr / DashieTM b864dc9caf fix: Bluetooth and wifi restart 2023-12-13 14:14:57 +01:00
Fabio Lenherr / DashieTM cca11a50ee chore: Revert to all system bus access tue to performance 2023-12-13 01:44:56 +01:00
Fabio Lenherr / DashieTM 22ed037d1f chore: Bump version of daemon and lib 2023-12-13 01:23:36 +01:00
Fabio Lenherr / DashieTM e1b027a68d fix: Use metered properly 2023-12-13 00:41:14 +01:00
Fabio Lenherr / DashieTM 4b09745fce chore: Bump version of daemon and lib 2023-12-12 23:12:43 +01:00
Fabio Lenherr / DashieTM ecfeca65e0 fix: Fix type for daemon 2023-12-12 23:04:57 +01:00
Fabio Lenherr / DashieTM 3ed909a5e4 chore: Use namespace with flatpak compatibility 2023-12-12 22:35:16 +01:00
Fabio Lenherr / DashieTM 770bdda317 chore: Add license to cargo 2023-12-12 20:53:22 +01:00
Fabio Lenherr / DashieTM 42477b9b02 chore: Prelease 2023-12-12 20:52:18 +01:00
takotori 83d31000eb
Merge pull request #82 from Xetibo/ina
Add shortname hint to searchbar placeholder
2023-12-12 20:51:06 +01:00
takotori 14727e25f4 Add shortname hint to searchbar placeholder 2023-12-12 20:44:31 +01:00
takotori f1f9d05cf4
Merge pull request #81 from Xetibo/ina
Ina
2023-12-12 20:40:41 +01:00
Fabio Lenherr / DashieTM c9d1282e62 chore: Remove unused import 2023-12-12 20:39:59 +01:00
Fabio Lenherr / DashieTM 5ec6de95c0 feat: Add proper popover menu and shortcuts 2023-12-12 20:39:59 +01:00
takotori 1ecc369d34 partly fix wifi options 2023-12-12 20:39:36 +01:00
takotori 13321039be
Merge pull request #80 from Xetibo/dashie
feat: Add basic shortcuts and fix popover
2023-12-12 08:15:45 +01:00
Fabio Lenherr / DashieTM 923137651c chore: Remove unused import 2023-12-12 02:13:27 +01:00
Fabio Lenherr / DashieTM 1428be6e2e feat: Add proper popover menu and shortcuts 2023-12-12 02:12:58 +01:00
takotori b422a50efd
Merge pull request #78 from Xetibo/ina
make wifi options a little bit better
2023-12-11 22:16:39 +01:00
takotori aba894f3a8 fix remove saved wifi entry 2023-12-11 22:16:20 +01:00
dashie b9ed39f85b chore: Fix warning 2023-12-11 22:01:28 +00:00
takotori 445c4d06fe make edit button from saved wifi entry working again 2023-12-11 22:01:07 +01:00
Dashie d468546c28
Merge pull request #79 from Xetibo/dashie
fix: Get initial mute status for default sink and source
2023-12-11 20:57:41 +00:00
dashie 08700b824f fix: Get initial mute status for default sink and source 2023-12-11 21:56:36 +00:00
takotori 209dc7f0af remove progress bars from audio entries 2023-12-11 21:54:36 +01:00
takotori f95dca41a4 fix save connection settings according to the book of resetlib 2023-12-11 21:45:19 +01:00
takotori e89de26b67 make bluetooth visibility settings consistent with other settingss 2023-12-11 17:22:16 +01:00
Dashie 8d9d5762a9
Merge pull request #76 from Xetibo/ina
make wifi entries consistent with other entries
2023-12-11 15:36:04 +00:00
Dashie 746b19e09d
Merge pull request #77 from Xetibo/dashie
Dashie
2023-12-11 15:34:57 +00:00
dashie 4ce01688f9 chore: Remove unused println 2023-12-11 16:32:27 +00:00
takotori a57c0c8701 make savedwifientry consistent with other entries 2023-12-11 15:51:29 +01:00
takotori 257d6753f1 bump reset lib version
fix parameter type
2023-12-11 15:07:44 +01:00
takotori 8c5595ea2a remove testing cmb 2023-12-11 15:00:29 +01:00
takotori d3ad017fc0 make wifi entries consistent with other entries 2023-12-11 14:59:30 +01:00
dashie a5c4a335c5 fix: Don't readd wifi adapter after turning off wifi 2023-12-11 13:52:33 +00:00
dashie 323aab4aa8 feat: Set volume as default 2023-12-11 12:57:11 +00:00
takotori 6263ac59bf
Merge pull request #75 from Xetibo/ina
fix big oopsie
2023-12-11 13:12:51 +01:00
dashie 3b94bd76f3 feat: Add standard page home 2023-12-11 11:53:49 +00:00
dashie b6b3ebfd64 fix: Don't show empty ssid 2023-12-09 19:09:51 +00:00
dashie c2557a714f fix: spinloop until listener ready 2023-12-09 14:23:15 +00:00
Fabio Lenherr / DashieTM 248fc9f526 chore: Bump daemon version 2023-12-09 13:41:41 +01:00
Fabio Lenherr / DashieTM 049a846c36 fix: Update default sink and source after setting it 2023-12-09 13:39:38 +01:00
dashie d3bc2edf88 chore: Update flatpak lock 2023-12-08 23:58:51 +00:00
dashie 286302354e feat: Add dynamic bluetooth buttons 2023-12-08 23:55:04 +00:00
Fabio Lenherr / DashieTM 741d678ffd merge 2023-12-06 23:40:47 +01:00
takotori 3821cf48c2 remove unused cmb template 2023-12-06 19:51:22 +01:00
takotori 4142f3f3a8 rename button 2023-12-06 19:50:22 +01:00
takotori 0184fba8b4 simplify bluetooth entry 2023-12-06 19:48:54 +01:00
Fabio Lenherr / DashieTM 8518d6e247 chore: Show address for bluetooth 2023-12-06 19:15:39 +01:00
Fabio Lenherr / DashieTM 64a5a67e29 chore: Use less margin 2023-12-06 19:03:31 +01:00
Fabio Lenherr / DashieTM c4af8696a8 chore: Use actionrow 2023-12-06 17:54:08 +01:00
Fabio Lenherr / DashieTM c5c5f6761e chore: Update lib 2023-12-06 17:14:24 +01:00
Fabio Lenherr / DashieTM 0eb85fdf7c chore: Make window bigger by default 2023-12-06 11:50:18 +01:00
Fabio Lenherr / DashieTM 7fc5d3d404 chore: Make window bigger by default 2023-12-06 11:48:17 +01:00
Fabio Lenherr / DashieTM 4836f1d8c2 feat: Add dynamic window expansion 2023-12-06 11:47:47 +01:00
Fabio Lenherr / DashieTM e03dad964b feat: Add proper wifi enable/disable 2023-12-05 22:25:49 +01:00
Fabio Lenherr / DashieTM ce91201192 feat: Add bluetooth adapter settings 2023-12-05 22:25:49 +01:00
Dashie 512cba018b
Merge pull request #71 from Xetibo/dashie
chore: snake_case all
2023-12-05 16:26:39 +01:00
Fabio Lenherr / DashieTM d50ee65bdd chore: make all snake_case 2023-12-05 16:15:24 +01:00
Fabio Lenherr / DashieTM 391182607d chore: snake_case all but UI templates 2023-12-05 15:30:04 +01:00
Fabio Lenherr / DashieTM 9f0ca6e8bf chore: Update Lib 2023-12-04 17:00:29 +01:00
Dashie 9f3e2146fd
Merge pull request #70 from Xetibo/ina2
Ina2
2023-12-04 12:23:21 +01:00
takotori d79ebeddd2 rename bluetoothdevice 2023-12-04 11:31:56 +01:00
takotori 55a6e247fa merge dashie 2023-12-04 10:30:46 +01:00
takotori a3866294af some things 2023-12-04 10:30:46 +01:00
takotori 593c6b7627 Implement password 2023-12-04 10:19:26 +01:00
Fabio Lenherr / DashieTM c5fb9611c9 fix: Use connect instead of pairing, as pairing does nothing currently 2023-12-03 22:37:01 +01:00
Fabio Lenherr / DashieTM 736bcb8e4d chore: Code cleanup 2023-12-03 19:22:31 +01:00
Fabio Lenherr / DashieTM 959d1e735f feat: Add wifi and bluetooth device switching 2023-12-03 19:08:07 +01:00
Fabio Lenherr / DashieTM b2b4ae0661 feat: Initial work on bluetooth on and off 2023-12-02 17:13:15 +01:00
Fabio Lenherr / DashieTM 4a34144ebc chore: Code cleanup 2023-12-02 17:07:17 +01:00
Fabio Lenherr / DashieTM 4a7fa8c584 feat: Allow Wifi Deactivation 2023-12-02 17:01:39 +01:00
takotori 1768f9e2ce hide out of scope features 2023-12-02 15:48:23 +01:00
takotori 2e034f848e Implement wifi options route 2023-12-02 15:48:23 +01:00
Dashie 1578fc3dac
Merge pull request #67 from Xetibo/ina
Wifi Address entry
2023-12-02 13:12:17 +01:00
takotori 2a622ee164 fix X (formerly known as Twitter) 2023-12-02 13:04:55 +01:00
takotori f32b08d842 update cargo.toml 2023-12-02 12:59:30 +01:00
takotori cd980350e4 Implement wifiaddressentry 2023-12-02 12:59:26 +01:00
takotori 29eea9ce4a
Merge pull request #66 from Xetibo/dashie
feat: Implement Bluetoothdevice changed and code cleanup
2023-12-02 12:45:05 +01:00
Fabio Lenherr c921e14c03 chore: Code cleanup 2023-12-01 14:31:33 +01:00
Fabio Lenherr 921d5e7fe6 chore: Code cleanup 2023-12-01 14:31:14 +01:00
Fabio Lenherr / DashieTM f8c27fd498 feat: Add bluetooth changed event 2023-11-30 17:36:37 +01:00
takotori ff90e12bde
Merge pull request #65 from Xetibo/dashie
feat: Implement new Settings API
2023-11-29 20:32:54 +01:00
Fabio Lenherr / DashieTM ba49da2317 feat: Allow pairings to be deleted 2023-11-29 20:22:37 +01:00
Fabio Lenherr / DashieTM 35d2174136 feat: Implement new event API 2023-11-29 01:33:00 +01:00
takotori 9b93d22085 clear search bar on clicking on setting 2023-11-28 09:49:18 +01:00
Dashie 4f1eacf56e
Merge pull request #64 from Xetibo/ina
Improve UI
2023-11-27 12:28:22 +01:00
takotori f7c95cd4a1 Add ellipsis to labels in ComboRow 2023-11-27 12:19:31 +01:00
takotori 00ffb7faf9 extract signallistitemfactory to utils 2023-11-27 11:31:28 +01:00
takotori d3afe5675e Add apply button for wifi options
Start setting values in connection
2023-11-26 16:16:37 +01:00
takotori c6efced326 Improve UI 2023-11-26 11:25:31 +01:00
takotori 02e63f5984
Merge pull request #63 from Xetibo/dashie
feat: Wifi event handling improvements
2023-11-25 16:31:28 +01:00
Fabio Lenherr / DashieTM 0cc8e557a8 chore: Bump version of daemon 2023-11-25 16:28:25 +01:00
Fabio Lenherr / DashieTM 75fbb062fc feat: Add Wifi Device Changes 2023-11-25 16:26:48 +01:00
Fabio Lenherr / DashieTM 5c99e7e59a wat 2023-11-23 10:28:48 +01:00
Fabio Lenherr / DashieTM e1cd11e601 chore: Code cleanup 2023-11-23 10:23:51 +01:00
Fabio Lenherr / DashieTM eed10e8b63 feat: Improve Wifi event handling 2023-11-22 17:50:12 +01:00
Dashie afe311a16b
Merge pull request #61 from Xetibo/ina
Ina
2023-11-21 20:26:19 +01:00
takotori dd2856261b fix some audio stuff 2023-11-21 18:59:37 +01:00
takotori 2e8c7eda33
Merge pull request #62 from Xetibo/dashie
Dashie
2023-11-21 17:00:58 +01:00
takotori 072d516ad1 Improve UI 2023-11-21 16:55:22 +01:00
Fabio Lenherr / DashieTM e48a5d1854 fix: Make sure listener is active before UI 2023-11-21 15:54:26 +01:00
takotori d9d5d2960d remove placeholder breadcrumb 2023-11-21 15:00:07 +01:00
takotori babc0e8cc4 Add factory for dropdowns
Replace ListEntry with libadwaita components
2023-11-21 14:21:56 +01:00
Fabio Lenherr 4e9d98fddb feat: Allow audio listener to be deactivated 2023-11-21 13:59:16 +01:00
takotori ba48f5ba33 Fix cmb
Add new fields in general wifi options
2023-11-21 10:47:08 +01:00
takotori ed7b814e62 Add Wifi Options (UI only) 2023-11-20 18:02:21 +01:00
takotori 5311bb8c4c Add wifiOptions (ui only) 2023-11-20 17:59:01 +01:00
Fabio Lenherr / DashieTM 1080a03b8b fix: Don't use popups for wifi 2023-11-19 23:52:16 +01:00
takotori d2c982145d
Merge pull request #60 from Xetibo/dashie
feat: Audio Device configuration and initial bluetooth handling
2023-11-19 19:08:23 +01:00
Fabio Lenherr / DashieTM b68bc0ab77 fix: Add and remove dropdown entries dynamically 2023-11-19 17:57:11 +01:00
Fabio Lenherr / DashieTM cb3dd257f1 chore: Code cleanup 2023-11-19 02:37:11 +01:00
Fabio Lenherr / DashieTM 192dacb333 chore: Bump version of daemon 2023-11-19 02:33:39 +01:00
Fabio Lenherr / DashieTM b28b736697 fix: Load audio listener properly 2023-11-19 02:31:41 +01:00
Fabio Lenherr / DashieTM 9108ab0d74 feat: Add initial Bluetooth functionality 2023-11-18 22:15:09 +01:00
Fabio Lenherr / DashieTM 03fc3790c0 fix: Use buffer for all audio sliders 2023-11-18 15:28:23 +01:00
Fabio Lenherr / DashieTM 5a0e5d86bb chore: Bump version of daemon 2023-11-18 15:10:09 +01:00
Fabio Lenherr / DashieTM 52445b9ba0 fix: Use buffer for sending volume updates 2023-11-18 15:08:56 +01:00
Dashie 19ba9f796e
Merge pull request #59 from Xetibo/dashie
fix: Use proper index for sink and sources
2023-11-17 00:46:28 +01:00
Fabio Lenherr / DashieTM 67f3457e3a fix: Use proper index for sink and sources 2023-11-16 23:21:52 +01:00
takotori 70a8d3fdb8
Merge pull request #58 from Xetibo/dashie
feat: Add audio events
2023-11-16 10:23:07 +01:00
Fabio Lenherr / DashieTM c659938c50 fix: Use microphone icons for input 2023-11-16 10:08:41 +01:00
Fabio Lenherr / DashieTM b6f447b11c chore: fix flatpak permissions 2023-11-16 01:50:23 +01:00
Fabio Lenherr / DashieTM b160e755f9 chore: Bump version of daemon 2023-11-16 01:20:02 +01:00
Fabio Lenherr / DashieTM 058dbb6355 chore: Bump version of daemon 2023-11-15 23:43:50 +01:00
Fabio Lenherr / DashieTM fcd894aea3 chore: Remove unused imports 2023-11-15 23:26:54 +01:00
Fabio Lenherr / DashieTM 9cc6fe319d chore: Bump version of daemon 2023-11-15 23:25:18 +01:00
Fabio Lenherr / DashieTM af06f8da32 fix: Use threading for every single dbus call on audio 2023-11-15 23:24:35 +01:00
Fabio Lenherr / DashieTM ed905cd075 chore: underscore temporary variable 2023-11-15 23:06:09 +01:00
Fabio Lenherr / DashieTM 225b0f6975 chore: Bump version of daemon 2023-11-15 23:04:40 +01:00
Fabio Lenherr / DashieTM 6ad3f07cf3 feat: Add all audio events 2023-11-15 23:02:27 +01:00
Fabio Lenherr / DashieTM a84c71d9e1 feat: Add change sink event 2023-11-15 19:43:39 +01:00
Fabio Lenherr / DashieTM 8ef97ac0d9 feat: Add basic event handling for audio 2023-11-15 19:32:16 +01:00
Dashie ac266d0214
Merge pull request #57 from Xetibo/dashie
chore: Include instructions for flatpak build
2023-11-15 15:22:51 +01:00
Fabio Lenherr / DashieTM 58fc09fd1d chore: Include instructions for flatpak build 2023-11-15 15:22:13 +01:00
Fabio Lenherr / DashieTM 01f6e32580 chore: Bump daemon version 2023-11-15 15:06:38 +01:00
takotori e0375b7b45
Merge pull request #55 from Xetibo/dashie
feat: Add dropdowns
2023-11-15 14:59:38 +01:00
Fabio Lenherr / DashieTM 0c286af82c feat: Add dropdowns 2023-11-15 14:50:00 +01:00
takotori 98fb183963
Merge pull request #54 from Xetibo/dashie
feat: Add functionality to volume sliders and mute buttons
2023-11-15 10:40:25 +01:00
Fabio Lenherr / DashieTM c619284146 feat: Add default Sink and Source Switching 2023-11-15 02:35:20 +01:00
Fabio Lenherr / DashieTM dbcf2ab635 fix: Improve performance by passing smaller datatypes to dbus 2023-11-14 22:41:00 +01:00
Fabio Lenherr / DashieTM c1984b5b79 feat: Add basic functionality to all audio usecases 2023-11-14 21:25:39 +01:00
Fabio Lenherr 2f1099267a chore: Rename UI files 2023-11-14 18:22:10 +01:00
Dashie ed00b26749
Merge pull request #53 from Xetibo/dashie
chore: Bump version of daemon
2023-11-13 21:34:50 +01:00
Fabio Lenherr / DashieTM 21b96428f0 chore: Bump version of daemon 2023-11-13 21:34:25 +01:00
takotori 07c65b5af9
Merge pull request #52 from Xetibo/dashie
feat: Add initial Sound Mapping
2023-11-13 19:11:27 +01:00
Fabio Lenherr / DashieTM 5999bb1b45 feat: Add initial Sound Mapping 2023-11-13 18:12:36 +01:00
takotori 679b4f0331
Merge pull request #51 from Xetibo/dashie
Add stop listener and replace existing entries
2023-11-13 11:01:10 +01:00
Fabio Lenherr / DashieTM a62288be07 chore: Remove unused imports 2023-11-13 10:31:53 +01:00
Fabio Lenherr / DashieTM e3b95d7540 feat: Add stop listener 2023-11-13 10:28:48 +01:00
Fabio Lenherr / DashieTM f5f6246ad1 feat: Add remove event for wifi entries 2023-11-12 23:06:31 +01:00
Fabio Lenherr / DashieTM 12f92ac597 feat: Implement working signal receiver 2023-11-12 22:10:15 +01:00
Dashie 97e81a617d
Merge pull request #50 from Xetibo/dashie
fix: Close popover menu on click
2023-11-12 18:14:19 +01:00
Fabio Lenherr / DashieTM 84f56666b0 fix: Close popover menu on click 2023-11-12 18:13:47 +01:00
takotori 5b7ce51667
Merge pull request #48 from Xetibo/dashie
Add Wifi Implementation and rework UI
2023-11-12 17:47:58 +01:00
takotori 142cc82c17
Merge pull request #47 from Xetibo/ina
Implement Microphone UI
2023-11-12 17:43:39 +01:00
takotori 35c58e2fcd Implement Microphone UI
Improve Audio UI
You won't believe what happened (gone sexual)
2023-11-12 17:43:01 +01:00
Fabio Lenherr / DashieTM d07180e2c7 fix: Merge 2023-11-12 13:17:19 +01:00
Dashie ca77df98e1
Merge pull request #45 from Xetibo/ina
Add title and border to settingbox
2023-11-12 12:59:39 +01:00
Dashie 831baee601
Merge branch 'dashie' into ina 2023-11-12 12:57:08 +01:00
Fabio Lenherr / DashieTM 59a6a9a14d chore: Update cmb file 2023-11-12 12:42:06 +01:00
Fabio Lenherr / DashieTM 9cad376a40 feat: Add saved WifiList 2023-11-12 03:41:29 +01:00
DashieTM bbfd07688d fix: Execute connect function with wifi connect button 2023-11-11 23:00:38 +01:00
Fabio Lenherr / DashieTM 7d0fc0b9e7 feat: Add popups for password 2023-11-11 17:55:59 +01:00
takotori 9f3815476d Add title and border to settingbox
Fix padding around window
Add some styling
2023-11-11 14:34:32 +01:00
takotori 179a2b44cc
Merge pull request #44 from Xetibo/dashie
Add Wifi integration
2023-11-11 11:13:13 +01:00
Fabio Lenherr / DashieTM 5575acf00f feat: Add popup 2023-11-11 11:09:07 +01:00
Fabio Lenherr / DashieTM 616792eaad fix: Unwrap result to get proper value 2023-11-10 22:14:45 +01:00
Fabio Lenherr / DashieTM 918c863192 feat: Add better daemon check 2023-11-10 22:08:03 +01:00
Fabio Lenherr / DashieTM d00c76fa9e feat: Add inbuilt daemon and create preference/shorcut window shell 2023-11-10 22:00:39 +01:00
Fabio Lenherr / DashieTM 2228c11c3c feat: Add gesture click to wifi -> dbus connection 2023-11-10 19:31:32 +01:00
Dashie eb4c9bb2a6
Merge pull request #43 from Xetibo/ina
Ina
2023-11-07 16:44:39 +01:00
takotori 919f39ce7a add microphone to resources 2023-11-07 16:39:30 +01:00
takotori 7af48c720f Add bluetooth ui in navigationview 2023-11-07 16:27:07 +01:00
takotori a0e3f5fbee cleanup 2023-11-07 15:46:33 +01:00
takotori 60760d779f Fix typo
Export cambalache to ui
2023-11-07 15:43:23 +01:00
takotori af7776d62a Add templates for box and listboxrow
kekw
2023-11-07 15:43:23 +01:00
145 changed files with 16762 additions and 2371 deletions

1
.direnv/flake-profile Symbolic link
View file

@ -0,0 +1 @@
flake-profile-4-link

View file

@ -0,0 +1 @@
/nix/store/30prmd5nyydss0bcs7d578grjav6i7x3-nix-shell-env

2
.envrc Normal file
View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
use flake

View file

@ -18,4 +18,4 @@ jobs:
uses: softprops/action-gh-release@v1
with:
files: |
reset-${{github.ref_name}}-0-x86_64.pkg.tar.zst
ReSet-${{github.ref_name}}-0-x86_64.pkg.tar.zst

View file

@ -1,7 +1,6 @@
on:
release:
types: [ created ]
types: [created]
jobs:
release:
runs-on: [self-hosted, ubuntu]
@ -13,23 +12,26 @@ jobs:
profile: minimal
toolchain: nightly
- name: Build rust package
run: cargo build --release --verbose
- name: Build Flatpak
run: |
cd flatpak
python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json
flatpak-builder build org.xetibo.ReSet.json --force-clean
flatpak build-export export build
flatpak build-bundle export reset.flatpak org.xetibo.ReSet
cargo build --release --verbose
- name: Build Flatpak
run: "cd flatpak\npython3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json \nflatpak-builder build org.Xetibo.ReSet.json --force-clean \nflatpak build-export export build\nflatpak build-bundle export reset.flatpak org.Xetibo.ReSet\n"
- name: Build Ubuntu package
run: |
cp ./target/release/reset ./debian/.
mkdir -p ./debian/usr
mkdir -p ./debian/usr/bin
mkdir -p ./debian/usr/share
mkdir -p ./debian/usr/share/applications
mkdir -p ./debian/usr/share/pixmaps
cp ./target/release/ReSet ./debian/usr/bin/ReSet
cp ./ReSet.desktop ./debian/usr/share/applications/.
cp ./src/resources/icons/ReSet.svg ./debian/usr/share/pixmaps/.
dpkg-deb --build debian
mv debian.deb reset.deb
mv debian.deb ReSet.deb
- name: Release
uses: softprops/action-gh-release@v1
with:
files: |
target/release/reset
target/release/ReSet
flatpak/reset.flatpak
reset.deb
ReSet.deb

View file

@ -1,37 +1,33 @@
name: Rust
# inspired by https://github.com/danth/stylix/blob/master/.github/workflows/docs.yml
name: Rust-build
on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
branches: ["main"]
jobs:
build:
runs-on: [self-hosted, ubuntu]
name: Build
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: nightly-rust
uses: actions-rs/toolchain@v1
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
profile: minimal
toolchain: nightly
- name: run code coverage
uses: actions-rs/tarpaulin@v0.1
github-token: ${{ secrets.GITHUB_TOKEN }}
extra-conf: |
extra-experimental-features = nix-command flakes
- name: Set up cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: action
uses: cachix/install-nix-action@v25
with:
version: '0.15.0'
args: '-- --test-threads 1'
- name: upload code coverage
uses: actions/upload-artifact@v1
nix_path: nixpkgs=channel:nixos-unstable
- name: cache
uses: cachix/cachix-action@v14
with:
name: code-coverage-report
path: cobertura.xml
name: reset
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
run: nix -L build github:${{ github.repository }}/${{ github.sha }} --no-write-lock-file

4
.gitignore vendored
View file

@ -9,10 +9,6 @@ flatpak/reset.flatpak
pkg/
*.pkg.tar.zst
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

1434
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,32 @@
[package]
name = "reset"
version = "0.1.0"
version = "2.0.0"
edition = "2021"
description = "A wip universal Linux settings application."
repository = "https://github.com/Xetibo/ReSet"
license = "GPL-3.0-or-later"
[[bin]]
name = "ReSet"
path = "src/main.rs"
[dependencies]
adw = { version = "0.5.3", package = "libadwaita", features = ["v1_4"]}
reset_daemon = "2.2.0"
re_set-lib = "5.2.5"
adw = { version = "0.6.0", package = "libadwaita", features = ["v1_4"] }
dbus = "0.9.7"
gtk = { version = "0.7.3", package = "gtk4", features = ["v4_12"]}
gtk = { version = "0.8.1", package = "gtk4", features = ["v4_12"] }
glib = "0.19.3"
tokio = { version = "1.36.0", features = [
"rt",
"time",
"net",
"macros",
"rt-multi-thread",
"sync",
] }
fork = "0.1.23"
ipnetwork = "0.20.0"
[build-dependencies]
glib-build-tools = "0.18.0"
glib-build-tools = "0.19.0"

View file

@ -1,12 +1,15 @@
# Maintainer: Fabio Lenherr <dashie@dashie.org>
pkgname=reset
pkgver=0.1
pkgname=ReSet
pkgver=2.0.0
pkgrel=0
arch=('x86_64')
pkgdir="/usr/bin/${pkgname}"
pkgdesc="A wip universal Linux settings application."
depends=('rust' 'gtk4' 'dbus')
depends=('gtk4' 'dbus' 'libadwaita')
optdepends=('pipewire-pulse' 'networkmanager' 'bluez')
makedepends=('rust')
build() {
cargo build --release
@ -16,5 +19,5 @@ package() {
cd ..
install -Dm755 target/release/"$pkgname" "$pkgdir"/usr/bin/"$pkgname"
install -Dm644 "$pkgname.desktop" "$pkgdir/usr/share/applications/$pkgname.desktop"
install -Dm644 "src/resources/icons/ReSet.svg" "$pkgdir/usr/share/pixmaps/ReSet.svg"
install -Dm644 "src/resources/icons/$pkgname.svg" "$pkgdir/usr/share/pixmaps/$pkgname.svg"
}

140
README.md
View file

@ -1,3 +1,141 @@
<div align = center>
# ReSet
A wip universal Linux settings application.
![Logo of ReSet](./assets/ReSet.png)
A window manager/compositor agnostic settings application for Linux written in rust and gtk4.
</div>
## Features
- Bluetooth via bluez
- Audio via PulseAudio
- Wi-Fi via NetworkManager
## Screenshots
<div align = center>
### Audio
<img alt="Audio Screenshot of ReSet" src="./assets/reset_audio.png" width="80%">
### Wi-Fi
<img alt="Wi-Fi Screenshot of ReSet" src="./assets/reset_wifi.png" width="80%">
### Bluetooth
<img alt="Bluetooth Screenshot of ReSet" src="./assets/reset_bluetooth.png" width="80%">
</div>
## Plugins
ReSet features a plugin system by loading dynamic libraries for both the daemon and the ReSet graphical user interface.
A list of official plugins, installation guides and their documentation can be found at [ReSet-Plugins](https://github.com/Xetibo/ReSet-Plugins).
### Installation
Plugins are loaded either from `/usr/lib/reset` or `~/.config/reset/plugins`. In order to install the plugin, either install a distribution specific package that places the library into the specified system folder, or place the library in the plugins folder in your config directory.
Note, after installation, please move to confirmation.
### Confirmation
In order for your plugins to load, you have to define them in `.config/reset/ReSet.toml`.
This is done to avoid loading of arbitrary plugins that might be placed within this folder by accident.
```toml
plugins = ["libreset_monitors.so", "libreset_keyboard_plugin.so"]
```
## Packaging
ReSet is available with the following packaging solutions:
### Flatpak
We are currently not published on flatpak due to issues with permissions.
This is being worked on...
Installation:
Download the flatpak package (reset.flatpak) from the release and install with the terminal.
```
flatpak install --user reset.flatpak
```
### Arch Package
<!-- AUR: -->
<!-- ```paru -S ReSet``` -->
Manually:
Download the package (ReSet-version-x86_64.pkg.tar.zst) from the releases tab and install it with pacman.
```
sudo pacman -U /path/to/reset
```
### Debian Package(Ubuntu 23.04 dependencies)
Download the package (ReSet.deb) from the releases tab and install it with apt.
```
sudo apt install ./path/to/reset
```
### NixOS/Home-manager
ReSet offers a flake with a home-manager module which you can use to declaratively install ReSet and plugins.
Here is an example configuration:
```nix
#inputs
reset.url = "github:Xetibo/ReSet";
reset-plugins.url = "github:Xetibo/ReSet-Plugins";
#installation and configuration
programs.ReSet.enable = true;
programs.ReSet.config.plugins = [
inputs.reset-plugins.packages."x86_64-linux".monitor
inputs.reset-plugins.packages."x86_64-linux".keyboard
];
programs.ReSet.config.plugin_config = {
#custom toml config
Keyboard = {
path = "/home/user/.config/reset/keyboard.conf";
};
};
```
### crates
```
cargo install reset
```
### Compiled Binary
The compiled binary is provided in the releases.
## Usage
Besides starting the application itself, a standalone daemon version ([ReSet-Daemon](https://github.com/Xetibo/ReSet-Daemon)) also exists, which is what provides the functionality for ReSet.\
It is therefore possible to use a different application as well for interacting with the daemon.
By default, the daemon is integrated into ReSet and is started automatically if no other daemon is found.
## Roadmap and Notes
- Accessibility Features
- Better Error handling
- Customizable shortcuts
- and more
### notes
This application was developed as a semester project/bachelor thesis for the Eastern Switzerland University of Applied Sciences.

View file

@ -2,7 +2,7 @@
Name=ReSet
GenericName=SettingsApplication
GenericName[de]=SettingsApplikation
Exec=reset
Exec=ReSet
Terminal=false
Type=Application
Keywords=settings;gtk;

BIN
assets/ReSet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
assets/reset_audio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
assets/reset_bluetooth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
assets/reset_wifi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View file

@ -1,5 +1,9 @@
Package: ReSet
Version: 0.1
Version: 2.0.0
Maintainer: DashieTM
Architecture: all
Description: A wip universal Linux settings application.
Homepage: https://github.com/Xetibo/ReSet
Build-Depends: rust
Depends: libadwaita-1-0, libgtk-4-1, dbus
Recommends: pipewire-pulse, network-manager, bluez

83
flake.lock Normal file
View file

@ -0,0 +1,83 @@
{
"nodes": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1738453229,
"narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1739446958,
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1739586408,
"narHash": "sha256-UN9hRKRE1eLU8C0cioTZubaCZQTA8NDc8/4vCpS5pS0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "3dbc0ce1c0690b83cfb9a9a51fbe90c3bc8f9916",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

62
flake.nix Normal file
View file

@ -0,0 +1,62 @@
{
description = "A wip universal Linux settings application.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = inputs @ { self, flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" "aarch64-linux" ];
perSystem =
{ config
, self'
, inputs'
, pkgs
, system
, ...
}:
{
_module.args.pkgs = import self.inputs.nixpkgs {
inherit system;
overlays = [
(import
inputs.rust-overlay
)
];
};
devShells.default = pkgs.mkShell {
inputsFrom = builtins.attrValues self'.packages;
packages = with pkgs; [
# (rust-bin.selectLatestNightlyWith
# (toolchain: toolchain.default))
rust-bin.nightly."2024-05-10".default
rust-analyzer
clippy
];
};
packages =
let
lockFile = ./Cargo.lock;
in
rec {
ReSet = pkgs.callPackage ./nix/default.nix { inherit inputs lockFile; };
default = ReSet;
};
};
flake = _: rec {
nixosModules.home-manager = homeManagerModules.default;
homeManagerModules = rec {
ReSet = import ./nix/hm.nix inputs.self;
default = ReSet;
};
};
};
}

17
flatpak/README.md Normal file
View file

@ -0,0 +1,17 @@
### instructions for building:
- `python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json`
- `flatpak-builder build org.xetibo.ReSet.json --force-clean`
- `flatpak build-export export build`
- `flatpak build-bundle export reset.flatpak org.xetibo.ReSet`
- you can also use the build.sh script provided
- note: if you are using a point release distribution(ubuntu, debian stable etc. please use the flatpak version of these commands -> flatpak run org.flatpak.Builder build...)
### instructions for installation:
`flatpak install --user reset.flatpak`
### permissions
currently ReSet uses permission on all devices, for some reason otherwise it can't access sound settings like volume changes etc.
This can likely be fixed by implementing portal integration later.

4
flatpak/build.sh Executable file
View file

@ -0,0 +1,4 @@
python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json
flatpak-builder build org.Xetibo.ReSet.json --force-clean
flatpak build-export export build
flatpak build-bundle export reset.flatpak org.Xetibo.ReSet

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
{
"app-id": "org.Xetibo.ReSet",
"runtime": "org.gnome.Platform",
"runtime-version": "46",
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-nightly"
],
"command": "ReSet",
"finish-args": [
"--socket=pulseaudio",
"--share=network",
"--share=ipc",
"--socket=fallback-x11",
"--socket=wayland",
"--device=dri",
"--device=all",
"--allow=bluetooth",
"--socket=system-bus",
"--socket=session-bus"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-nightly/bin"
},
"modules": [
{
"name": "reset",
"buildsystem": "simple",
"build-options": {
"env": {
"CARGO_HOME": "/run/build/reset/cargo"
}
},
"build-commands": [
"cargo --offline fetch --manifest-path Cargo.toml --verbose",
"cargo --offline build --verbose",
"install -Dm755 ./target/debug/ReSet -t /app/bin/",
"install -Dm644 ./src/resources/icons/ReSet.svg /app/share/icons/hicolor/scalable/apps/org.Xetibo.ReSet.svg",
"install -Dm644 ./flatpak/org.Xetibo.ReSet.desktop /app/share/applications/org.Xetibo.ReSet.desktop"
],
"sources": [
{
"type": "dir",
"path": ".."
},
"cargo-sources.json"
]
}
]
}

View file

@ -2,10 +2,10 @@
Name=ReSet
GenericName=SettingsApplication
GenericName[de]=SettingsApplikation
Exec=reset
Exec=ReSet
Terminal=false
Type=Application
Keywords=settings;gtk;
Icon=org.xetibo.ReSet
Icon=org.Xetibo.ReSet
Categories=Utility;GTK;
StartupNotify=false

View file

@ -1,19 +1,27 @@
{
"app-id": "org.xetibo.ReSet",
"app-id": "org.Xetibo.ReSet",
"runtime": "org.gnome.Platform",
"runtime-version": "45",
"runtime-version": "46",
"sdk": "org.gnome.Sdk",
"sdk-extensions": ["org.freedesktop.Sdk.Extension.rust-stable"],
"command": "reset",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-nightly"
],
"command": "ReSet",
"finish-args": [
"--socket=session-bus",
"--socket=pulseaudio",
"--share=network",
"--share=ipc",
"--socket=fallback-x11",
"--socket=wayland",
"--device=dri"
"--device=dri",
"--device=all",
"--allow=bluetooth",
"--socket=system-bus",
"--socket=session-bus",
"--persist=~/.config/reset:create"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin"
"append-path": "/usr/lib/sdk/rust-nightly/bin"
},
"modules": [
{
@ -27,9 +35,9 @@
"build-commands": [
"cargo --offline fetch --manifest-path Cargo.toml --verbose",
"cargo --offline build --release --verbose",
"install -Dm755 ./target/release/reset -t /app/bin/",
"install -Dm644 ./src/resources/icons/ReSet.svg /app/share/icons/hicolor/scalable/apps/org.xetibo.ReSet.svg",
"install -Dm644 ./flatpak/org.xetibo.ReSet.desktop /app/share/applications/org.xetibo.ReSet.desktop"
"install -Dm755 ./target/release/ReSet -t /app/bin/",
"install -Dm644 ./src/resources/icons/ReSet.svg /app/share/icons/hicolor/scalable/apps/org.Xetibo.ReSet.svg",
"install -Dm644 ./flatpak/org.Xetibo.ReSet.desktop /app/share/applications/org.Xetibo.ReSet.desktop"
],
"sources": [
{

69
nix/default.nix Normal file
View file

@ -0,0 +1,69 @@
{
rustPlatform,
rust-bin,
pulseaudio,
dbus,
gdk-pixbuf,
adwaita-icon-theme,
pkg-config,
wrapGAppsHook4,
gtk4,
libadwaita,
python312Packages,
flatpak,
flatpak-builder,
lib,
lockFile,
...
}: let
cargoToml = builtins.fromTOML (builtins.readFile ../Cargo.toml);
in
rustPlatform.buildRustPackage rec {
pname = "ReSet";
version = cargoToml.package.version;
src = ../.;
buildInputs = [
gtk4
libadwaita
pulseaudio
dbus
gdk-pixbuf
adwaita-icon-theme
python312Packages.aiohttp
python312Packages.toml
flatpak
flatpak-builder
];
cargoLock = {
inherit lockFile;
};
nativeBuildInputs = [
pkg-config
wrapGAppsHook4
rust-bin.nightly."2024-05-10".default
];
copyLibs = true;
postInstall = ''
install -D --mode=444 $src/${pname}.desktop $out/share/applications/${pname}.desktop
install -D --mode=444 $src/src/resources/icons/${pname}.svg $out/share/pixmaps/${pname}.svg
'';
# test is broken in nix for some reason
doInstallCheck = false;
doCheck = false;
meta = with lib; {
description = "A wip universal Linux settings application.";
homepage = "https://github.com/Xetibo/ReSet";
changelog = "https://github.com/Xetibo/ReSet/releases/tag/${version}";
license = licenses.gpl3;
maintainers = with maintainers; [DashieTM];
mainProgram = "ReSet";
};
}

80
nix/hm.nix Normal file
View file

@ -0,0 +1,80 @@
self: { config
, pkgs
, lib
, hm
, ...
}:
let
cfg = config.programs.ReSet;
defaultPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
in
{
meta.maintainers = with lib.maintainers; [ DashieTM ];
options.programs.ReSet = with lib; {
enable = mkEnableOption "ReSet";
package = mkOption {
type = with types; nullOr package;
default = defaultPackage;
defaultText = lib.literalExpression ''
ReSet.packages.''${pkgs.stdenv.hostPlatform.system}.default
'';
description = mdDoc ''
Package to run
'';
};
config = {
plugins = mkOption {
type = with types; listOf package;
default = null;
description = mdDoc ''
List of plugins to use, represented as a list of packages.
'';
};
plugin_config = mkOption {
type = with types; attrs;
default = { };
description = mdDoc ''
Toml values passed to the configuration for plugins to use.
'';
};
};
};
config =
let
fetchedPlugins =
if cfg.config.plugins == [ ]
then [ ]
else
builtins.map
(entry:
if lib.types.package.check entry
then "lib${lib.replaceStrings ["-"] ["_"] entry.pname}.so"
else "")
cfg.config.plugins;
in
lib.mkIf
cfg.enable
{
home.packages = lib.optional (cfg.package != null) cfg.package ++ cfg.config.plugins;
home.file = builtins.listToAttrs (builtins.map
(pkg: {
name = ".config/reset/plugins/lib${lib.replaceStrings ["-"] ["_"] pkg.pname}.so";
value = {
source = "${pkg}/lib/lib${lib.replaceStrings ["-"] ["_"] pkg.pname}.so";
};
})
cfg.config.plugins);
xdg.configFile."reset/ReSet.toml".source = (pkgs.formats.toml { }).generate "reset"
(lib.recursiveUpdate
{
plugins = fetchedPlugins;
}
cfg.config.plugin_config);
};
}

View file

@ -1,15 +0,0 @@
use adw::glib;
use adw::glib::Object;
use crate::components::audio::audioBoxImpl;
glib::wrapper! {
pub struct AudioBox(ObjectSubclass<audioBoxImpl::AudioBox>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl AudioBox {
pub fn new() -> Self {
Object::builder().build()
}
}

View file

@ -1,42 +0,0 @@
use gtk::{CompositeTemplate, DropDown, TemplateChild, glib};
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use crate::components::audio::audioBox;
use crate::components::audio::audioSource::AudioSourceEntry;
#[allow(non_snake_case)]
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetAudio.ui")]
pub struct AudioBox {
#[template_child]
pub resetOutputDevice: TemplateChild<DropDown>,
}
#[glib::object_subclass]
impl ObjectSubclass for AudioBox {
const NAME: &'static str = "resetAudio";
type Type = audioBox::AudioBox;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
AudioSourceEntry::ensure_type();
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl BoxImpl for AudioBox {}
impl ObjectImpl for AudioBox {}
impl ListBoxRowImpl for AudioBox {}
impl WidgetImpl for AudioBox {}
impl WindowImpl for AudioBox {}
impl ApplicationWindowImpl for AudioBox {}

View file

@ -1,15 +0,0 @@
use adw::glib;
use adw::glib::Object;
use crate::components::audio::audioSourceImpl;
glib::wrapper! {
pub struct AudioSourceEntry(ObjectSubclass<audioSourceImpl::AudioSourceEntry>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl AudioSourceEntry {
pub fn new() -> Self {
Object::builder().build()
}
}

View file

@ -1,42 +0,0 @@
use gtk::{Button, CompositeTemplate, glib, Image, Label, ProgressBar, Scale};
use gtk::subclass::prelude::*;
use crate::components::audio::audioSource;
#[allow(non_snake_case)]
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetAudioSourceEntry.ui")]
pub struct AudioSourceEntry {
#[template_child]
pub resetSourceIcon: TemplateChild<Image>,
#[template_child]
pub resetSourceName: TemplateChild<Label>,
#[template_child]
pub resetSourceMute: TemplateChild<Button>,
#[template_child]
pub resetVolumeSlider: TemplateChild<Scale>,
#[template_child]
pub resetVolumePercentage: TemplateChild<Label>,
#[template_child]
pub resetVolumeMeter: TemplateChild<ProgressBar>,
}
#[glib::object_subclass]
impl ObjectSubclass for AudioSourceEntry {
const NAME: &'static str = "resetAudioSourceEntry";
type Type = audioSource::AudioSourceEntry;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl BoxImpl for AudioSourceEntry {}
impl ObjectImpl for AudioSourceEntry {}
impl WidgetImpl for AudioSourceEntry {}

View file

@ -0,0 +1,589 @@
use std::{
sync::Arc,
time::{Duration, SystemTime},
};
use adw::prelude::{ComboRowExt, PreferencesRowExt};
use dbus::arg::{Arg, Get};
use glib::{
object::{Cast, IsA},
ControlFlow, Propagation,
};
use gtk::{
gio,
prelude::{BoxExt, ButtonExt, CheckButtonExt, ListBoxRowExt, RangeExt},
StringObject,
};
use re_set_lib::{
audio::audio_structures::{TAudioObject, TAudioStreamObject},
signals::{TAudioEventRemoved, TAudioObjectEvent, TAudioStreamEvent},
};
use crate::components::base::{error_impl::ReSetErrorImpl, list_entry::ListEntry};
use super::{
audio_box_utils::{
populate_audio_object_information, populate_cards, populate_streams,
refresh_default_audio_object,
},
audio_entry::{
new_entry, DBusFunction, TAudioBox, TAudioBoxImpl, TAudioEntry, TAudioEntryImpl,
TAudioStream, TAudioStreamImpl,
},
audio_functions::new_stream_entry,
audio_utils::audio_dbus_call,
};
pub fn mute_clicked_handler<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
audio_box: Arc<AudioBox>,
function: &'static DBusFunction,
) {
let imp = audio_box.box_imp();
let source = imp.default_audio_object();
let mut source = source.borrow_mut();
source.toggle_muted();
let icons = imp.icons();
let mute_button = imp.audio_object_mute();
if source.muted() {
mute_button.set_icon_name(icons.muted);
} else {
mute_button.set_icon_name(icons.active);
}
audio_dbus_call::<AudioBox, (), (u32, bool)>(
audio_box.clone(),
(source.index(), source.muted()),
function,
);
}
pub fn volume_slider_handler<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
audio_box: Arc<AudioBox>,
value: f64,
function: &'static DBusFunction,
) -> Propagation {
let imp = audio_box.box_imp();
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.volume_percentage().set_text(&percentage);
let source = imp.default_audio_object();
let source = source.borrow();
let index = source.index();
let channels = source.channels();
{
let mut time = imp.volume_time_stamp().borrow_mut();
if time.is_some() && time.unwrap().elapsed().unwrap() < Duration::from_millis(50) {
return Propagation::Proceed;
}
*time = Some(SystemTime::now());
}
audio_dbus_call::<AudioBox, (), (u32, u16, u32)>(
audio_box.clone(),
(index, channels, value as u32),
function,
);
Propagation::Proceed
}
pub fn dropdown_handler<
AudioObject: TAudioObject + Send + Sync,
StreamObject: TAudioStreamObject + Send + Sync,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
audio_box: Arc<AudioBox>,
dropdown: &adw::ComboRow,
function: &'static DBusFunction,
) -> ControlFlow {
let source_box_imp = audio_box.box_imp();
let source_box_ref = audio_box.clone();
let selected = dropdown.selected_item();
if selected.is_none() {
return ControlFlow::Break;
}
let selected = selected.unwrap();
let selected = selected.downcast_ref::<StringObject>().unwrap();
let selected = selected.string().to_string();
let source_map = source_box_imp.source_map();
let source_map = source_map.read().unwrap();
let source = source_map.get(&selected);
if source.is_none() {
return ControlFlow::Break;
}
let source = Arc::new(source.unwrap().1.clone());
gio::spawn_blocking(move || {
let result = audio_dbus_call::<AudioBox, (AudioObject,), (&String,)>(
source_box_ref.clone(),
(&source,),
function,
);
if result.is_none() {
return ControlFlow::Break;
}
refresh_default_audio_object::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(result.unwrap().0, source_box_ref.clone(), false);
ControlFlow::Continue
});
ControlFlow::Continue
}
pub fn populate_audio_objects<
AudioObject: TAudioObject + Arg + for<'z> Get<'z> + Send + Sync + 'static,
StreamObject: TAudioStreamObject + Send + Sync + for<'z> Get<'z> + Arg + 'static,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
audio_box: Arc<AudioBox>,
audio_objects_function: &'static DBusFunction,
default_audio_object_function: &'static DBusFunction,
set_default_audio_object_function: &'static DBusFunction,
get_audio_streams_function: &'static DBusFunction,
set_audio_object_volume_function: &'static DBusFunction,
set_audio_object_mute_function: &'static DBusFunction,
) {
gio::spawn_blocking(move || {
let sources = audio_dbus_call::<AudioBox, (Vec<AudioObject>,), ()>(
audio_box.clone(),
(),
audio_objects_function,
);
if sources.is_none() {
return;
}
let audio_objects = sources.unwrap().0;
{
let imp = audio_box.box_imp();
let list = imp.model_list();
let list = list.write().unwrap();
let map = imp.source_map();
let mut map = map.write().unwrap();
let model_index = imp.model_index();
let mut model_index = model_index.write().unwrap();
let audio_object = audio_dbus_call::<AudioBox, (AudioObject,), ()>(
audio_box.clone(),
(),
default_audio_object_function,
);
if let Some(audio_object) = audio_object {
imp.default_audio_object().replace(audio_object.0);
}
for audio_object in audio_objects.iter() {
let alias = audio_object.alias();
list.append(&alias);
map.insert(
alias.clone(),
(audio_object.index(), audio_object.name().clone()),
);
*model_index += 1;
}
}
populate_streams::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(audio_box.clone(), get_audio_streams_function);
populate_cards::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(audio_box.clone());
populate_audio_object_information::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(
audio_box,
audio_objects,
set_default_audio_object_function,
set_audio_object_volume_function,
set_audio_object_mute_function,
);
});
}
pub fn object_added_handler<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
Event: TAudioObjectEvent<AudioObject>,
>(
audio_box: Arc<AudioBox>,
ir: Event,
dummy_name: &'static str,
) -> bool {
glib::spawn_future(async move {
glib::idle_add_once(move || {
let audio_box = audio_box.clone();
let source_box_imp = audio_box.box_imp();
let object = ir.object_ref();
let object_index = object.index();
let alias = object.alias().clone();
let name = object.name().clone();
let mut is_default = false;
if source_box_imp.default_audio_object().borrow().name() == object.name() {
is_default = true;
}
let source_entry = new_entry::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(
is_default,
source_box_imp.default_check_button().clone(),
ir.object(),
audio_box.clone(),
);
let source_clone = source_entry.clone();
let entry = Arc::new(ListEntry::new(&*source_entry));
entry.set_activatable(false);
let list = source_box_imp.audio_object_list();
let mut list = list.write().unwrap();
list.insert(object_index, (entry.clone(), source_clone, alias.clone()));
source_box_imp.audio_objects().append(&*entry);
let map = source_box_imp.source_map();
let mut map = map.write().unwrap();
let index = source_box_imp.model_index();
let mut index = index.write().unwrap();
let model_list = source_box_imp.model_list();
let model_list = model_list.write().unwrap();
if model_list.string(*index - 1) == Some(dummy_name.into()) {
model_list.append(&alias);
model_list.remove(*index - 1);
map.insert(alias, (object_index, name));
source_box_imp.audio_object_dropdown().set_selected(0);
} else {
model_list.append(&alias);
map.insert(alias.clone(), (object_index, name));
if alias == dummy_name {
source_box_imp.audio_object_dropdown().set_selected(0);
}
*index += 1;
}
});
});
true
}
pub fn object_changed_handler<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
Event: TAudioObjectEvent<AudioObject>,
>(
audio_box: Arc<AudioBox>,
ir: Event,
function: &'static DBusFunction,
) -> bool {
let source = audio_dbus_call::<AudioBox, (String,), ()>(audio_box.clone(), (), function);
if source.is_none() {
return false;
}
let default_source = source.unwrap().0;
glib::spawn_future(async move {
glib::idle_add_once(move || {
let audio_box = audio_box.clone();
let box_imp = audio_box.box_imp();
let object = ir.object_ref();
let is_default = object.name() == default_source;
let volume = object.volume();
let volume = volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
let list = box_imp.audio_object_list();
let list = list.read().unwrap();
let entry = list.get(&object.index());
if entry.is_none() {
return;
}
let imp = entry.unwrap().1.entry_imp();
if is_default {
box_imp.volume_percentage().set_text(&percentage);
box_imp.volume_slider().set_value(*volume as f64);
box_imp.default_audio_object().replace(ir.object());
let icons = imp.icons();
let mute_button = imp.mute();
if object.muted() {
mute_button.set_icon_name(icons.muted);
} else {
mute_button.set_icon_name(icons.active);
}
imp.selected_audio_object().set_active(true);
} else {
imp.selected_audio_object().set_active(false);
}
imp.name().set_title(object.alias().as_str());
imp.volume_percentage().set_text(&percentage);
imp.volume_slider().set_value(*volume as f64);
let mute_button = imp.mute();
let icons = imp.icons();
if object.muted() {
mute_button.set_icon_name(icons.muted);
} else {
mute_button.set_icon_name(icons.active);
}
});
});
true
}
pub fn object_removed_handler<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
Event: TAudioEventRemoved,
>(
audio_box: Arc<AudioBox>,
ir: Event,
dummy_name: &'static str,
) -> bool {
glib::spawn_future(async move {
glib::idle_add_once(move || {
let audio_box = audio_box.clone();
let box_imp = audio_box.box_imp();
let entry: Option<(Arc<ListEntry>, Arc<AudioEntry>, String)>;
{
let list = box_imp.audio_object_list();
let mut list = list.write().unwrap();
entry = list.remove(&ir.index());
if entry.is_none() {
return;
}
}
box_imp.audio_objects().remove(&*entry.clone().unwrap().0);
let map = box_imp.source_map();
let mut map = map.write().unwrap();
let alias = entry.unwrap().2;
map.remove(&alias);
let index = box_imp.model_index();
let mut index = index.write().unwrap();
let model_list = box_imp.model_list();
let model_list = model_list.write().unwrap();
if *index == 1 {
model_list.append(dummy_name);
}
for entry in 0..*index {
if model_list.string(entry) == Some(alias.clone().into()) {
model_list.splice(entry, 1, &[]);
break;
}
}
if *index > 1 {
*index -= 1;
}
});
});
true
}
pub fn audio_stream_added_handler<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
Event: TAudioStreamEvent<StreamObject>,
>(
audio_box: Arc<AudioBox>,
ir: Event,
) -> bool {
glib::spawn_future(async move {
glib::idle_add_once(move || {
let audio_box = audio_box.clone();
let imp = audio_box.box_imp();
let list = imp.audio_object_stream_list();
let mut list = list.write().unwrap();
let index = ir.stream_ref().index();
let stream = new_stream_entry::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(audio_box.clone(), ir.stream());
let entry = Arc::new(ListEntry::new(&*stream));
entry.set_activatable(false);
list.insert(index, (entry.clone(), stream.clone()));
imp.audio_object_streams().append(&*entry);
});
});
true
}
pub fn audio_stream_changed_handler<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
Event: TAudioStreamEvent<StreamObject>,
>(
audio_box: Arc<AudioBox>,
ir: Event,
) -> bool {
let imp = audio_box.box_imp();
let alias: String;
{
let stream = ir.stream_ref();
let object_list = imp.audio_object_list();
let object_list = object_list.read().unwrap();
if let Some(alias_opt) = object_list.get(&stream.audio_object_index()) {
alias = alias_opt.2.clone();
} else {
alias = String::from("");
}
}
glib::spawn_future(async move {
glib::idle_add_once(move || {
let audio_box = audio_box.clone();
let box_imp = audio_box.box_imp();
let entry: Arc<AudioStream>;
let stream = ir.stream_ref();
{
let list = box_imp.audio_object_stream_list();
let list = list.read().unwrap();
let entry_opt = list.get(&stream.index());
if entry_opt.is_none() {
return;
}
entry = entry_opt.unwrap().1.clone();
}
let imp = entry.entry_imp();
let mute_button = imp.audio_object_mute();
let icons = imp.icons();
if stream.muted() {
mute_button.set_icon_name(icons.muted);
} else {
mute_button.set_icon_name(icons.active);
}
let name = stream.application_name().clone() + ": " + stream.name().as_str();
imp.audio_object_selection().set_title(name.as_str());
let volume = stream.volume();
let volume = volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.volume_percentage().set_text(&percentage);
imp.volume_slider().set_value(*volume as f64);
let index = box_imp.model_index();
let index = index.read().unwrap();
let model_list = box_imp.model_list();
let model_list = model_list.read().unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(alias.clone().into()) {
imp.audio_object_selection().set_selected(entry);
break;
}
}
});
});
true
}
pub fn audio_stream_removed_handler<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
Event: TAudioEventRemoved,
>(
audio_box: Arc<AudioBox>,
ir: Event,
) -> bool {
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = audio_box.box_imp();
let list = imp.audio_object_stream_list();
let mut list = list.write().unwrap();
let entry = list.remove(&ir.index());
if entry.is_none() {
return;
}
imp.audio_object_streams().remove(&*entry.unwrap().0);
});
});
true
}

View file

@ -0,0 +1,476 @@
use std::sync::Arc;
use adw::{prelude::ComboRowExt, prelude::PreferencesGroupExt};
use dbus::{
arg::{Arg, Get, ReadAll},
blocking::Connection,
message::SignalArgs,
Path,
};
use glib::{object::IsA, Variant};
use gtk::{
gio,
prelude::{ActionableExt, BoxExt, ButtonExt, CheckButtonExt, ListBoxRowExt, RangeExt},
};
use re_set_lib::{
audio::audio_structures::{Card, TAudioObject, TAudioStreamObject},
signals::{TAudioEventRemoved, TAudioObjectEvent, TAudioStreamEvent},
ERROR,
};
#[cfg(debug_assertions)]
use re_set_lib::{utils::macros::ErrorLevel, write_log_to_file};
use crate::components::{
base::{card_entry::CardEntry, error_impl::ReSetErrorImpl, list_entry::ListEntry},
utils::{create_dropdown_label_factory, set_combo_row_ellipsis, BASE, DBUS_PATH},
};
use super::{
audio_box_handlers::{
audio_stream_added_handler, audio_stream_changed_handler, audio_stream_removed_handler,
dropdown_handler, mute_clicked_handler, object_added_handler, object_changed_handler,
object_removed_handler, volume_slider_handler,
},
audio_const::GETCARDS,
audio_entry::{
new_entry, DBusFunction, TAudioBox, TAudioBoxImpl, TAudioEntry, TAudioEntryImpl,
TAudioStream, TAudioStreamImpl,
},
audio_functions::new_stream_entry,
audio_utils::audio_dbus_call,
};
pub fn setup_audio_box_callbacks<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl>,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
audio_box: &mut AudioBox,
) {
let imp = audio_box.box_imp();
let object_row = imp.audio_object_row();
object_row.set_activatable(true);
object_row.set_action_name(Some("navigation.push"));
object_row.set_action_target_value(Some(&Variant::from("devices")));
let cards_row = imp.cards_row();
cards_row.set_activatable(true);
cards_row.set_action_name(Some("navigation.push"));
cards_row.set_action_target_value(Some(&Variant::from("profileConfiguration")));
let stream_button = imp.audio_object_stream_button();
stream_button.set_activatable(true);
stream_button.set_action_name(Some("navigation.pop"));
let cards_back_button = imp.cards_button();
cards_back_button.set_activatable(true);
cards_back_button.set_action_name(Some("navigation.pop"));
let audio_object_dropdown = imp.audio_object_dropdown();
audio_object_dropdown.set_factory(Some(&create_dropdown_label_factory()));
set_combo_row_ellipsis(audio_object_dropdown.get());
}
pub fn populate_cards<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
source_box: Arc<AudioBox>,
) {
gio::spawn_blocking(move || {
let source_box_ref = source_box.clone();
let cards =
audio_dbus_call::<AudioBox, (Vec<Card>,), ()>(source_box.clone(), (), &GETCARDS);
if cards.is_none() {
return;
}
let cards = cards.unwrap().0;
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = source_box_ref.box_imp();
for card in cards {
imp.cards().add(&CardEntry::new(card));
}
});
});
});
}
pub fn populate_streams<
AudioObject: TAudioObject + Sync + Send + 'static,
StreamObject: TAudioStreamObject + Arg + for<'z> Get<'z> + Sync + Send + 'static,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
audio_box: Arc<AudioBox>,
function: &'static DBusFunction,
) {
let audio_box_ref = audio_box.clone();
gio::spawn_blocking(move || {
let streams =
audio_dbus_call::<AudioBox, (Vec<StreamObject>,), ()>(audio_box.clone(), (), function);
if streams.is_none() {
return;
}
let streams = streams.unwrap().0;
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = audio_box_ref.box_imp();
let mut list = imp.audio_object_stream_list().write().unwrap();
for stream in streams {
let index = stream.index();
let stream = new_stream_entry::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(audio_box.clone(), stream);
let stream_clone = stream.clone();
let entry = Arc::new(ListEntry::new(&*stream));
entry.set_activatable(false);
list.insert(index, (entry.clone(), stream_clone));
imp.audio_object_streams().append(&*entry);
}
});
});
});
}
pub fn refresh_default_audio_object<
AudioObject: TAudioObject + Sync + Send + 'static,
StreamObject: TAudioStreamObject + Arg + for<'z> Get<'z> + Sync + Send + 'static,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
new_audio_object: AudioObject,
audio_box: Arc<AudioBox>,
entry: bool,
) {
let volume = *new_audio_object.volume().first().unwrap_or(&0_u32);
let fraction = (volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = audio_box.box_imp();
if !entry {
let list = imp.audio_object_list().read().unwrap();
let entry = list.get(&new_audio_object.index());
if entry.is_none() {
return;
}
let entry_imp = entry.unwrap().1.entry_imp();
entry_imp.selected_audio_object().set_active(true);
} else {
let model_list = imp.model_list();
let model_list = model_list.read().unwrap();
for entry in 0..*imp.model_index().read().unwrap() {
if model_list.string(entry) == Some(new_audio_object.alias().clone().into()) {
imp.audio_object_dropdown().set_selected(entry);
break;
}
}
}
imp.volume_percentage().set_text(&percentage);
imp.volume_slider().set_value(volume as f64);
let icons = imp.icons();
let mute_button = imp.audio_object_mute();
if new_audio_object.muted() {
mute_button.set_icon_name(icons.muted);
} else {
mute_button.set_icon_name(icons.active);
}
imp.default_audio_object().replace(new_audio_object);
});
});
}
pub fn populate_audio_object_information<
AudioObject: TAudioObject + Sync + Send + 'static + Arg + for<'z> Get<'z>,
StreamObject: TAudioStreamObject + Arg + for<'z> Get<'z> + Sync + Send + 'static,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
audio_box: Arc<AudioBox>,
audio_objects: Vec<AudioObject>,
dropdown_function: &'static DBusFunction,
change_volume_function: &'static DBusFunction,
mute_function: &'static DBusFunction,
) {
glib::spawn_future(async move {
glib::idle_add_once(move || {
let source_box_ref_slider = audio_box.clone();
let source_box_ref_toggle = audio_box.clone();
let source_box_ref_mute = audio_box.clone();
let imp = audio_box.box_imp();
let default_sink = imp.default_audio_object().clone();
let source = default_sink.borrow();
let icons = imp.icons();
let mute_button = imp.audio_object_mute();
if source.muted() {
mute_button.set_icon_name(icons.muted);
} else {
mute_button.set_icon_name(icons.active);
}
let volume = source.volume();
let volume = volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.volume_percentage().set_text(&percentage);
imp.volume_slider().set_value(*volume as f64);
let list = imp.audio_object_list();
let mut list = list.write().unwrap();
for source in audio_objects {
let index = source.index();
let alias = source.alias().clone();
let mut is_default = false;
if imp.default_audio_object().borrow().name() == source.name() {
is_default = true;
}
let source_entry = new_entry::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(
is_default,
imp.default_check_button().clone(),
source,
audio_box.clone(),
);
let source_clone = source_entry.clone();
let entry = Arc::new(ListEntry::new(&*source_entry));
entry.set_activatable(false);
list.insert(index, (entry.clone(), source_clone, alias));
imp.audio_objects().append(&*entry);
}
let list = imp.model_list();
let list = list.read().unwrap();
imp.audio_object_dropdown().set_model(Some(&*list));
let name = imp.default_audio_object();
let name = name.borrow();
let index = imp.model_index();
let index = index.read().unwrap();
let model_list = imp.model_list();
let model_list = model_list.read().unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(name.alias().clone().into()) {
imp.audio_object_dropdown().set_selected(entry);
break;
}
}
imp.audio_object_dropdown()
.connect_selected_notify(move |dropdown| {
dropdown_handler(source_box_ref_toggle.clone(), dropdown, dropdown_function);
});
imp.volume_slider()
.connect_change_value(move |_, _, value| {
volume_slider_handler(
source_box_ref_slider.clone(),
value,
change_volume_function,
)
});
imp.audio_object_mute().connect_clicked(move |_| {
mute_clicked_handler(source_box_ref_mute.clone(), mute_function);
});
});
});
}
pub fn start_audio_box_listener<
AudioObject: TAudioObject,
StreamObject: TAudioStreamObject,
AudioEntry: TAudioEntry<AudioEntryImpl> + IsA<gtk::Widget>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl> + IsA<gtk::Widget>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
ObjectAdded: TAudioObjectEvent<AudioObject> + ReadAll + SignalArgs,
ObjectChanged: TAudioObjectEvent<AudioObject> + ReadAll + SignalArgs,
ObjectRemoved: TAudioEventRemoved + ReadAll + SignalArgs,
StreamAdded: TAudioStreamEvent<StreamObject> + ReadAll + SignalArgs,
StreamChanged: TAudioStreamEvent<StreamObject> + ReadAll + SignalArgs,
StreamRemoved: TAudioEventRemoved + ReadAll + SignalArgs,
>(
conn: Connection,
source_box: Arc<AudioBox>,
get_default_name_function: &'static DBusFunction,
dummy_name: &'static str,
) -> Connection {
// FUTURE TODO: make the failed logs generically sound -> deynamic output for both
let object_added =
ObjectAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let object_changed =
ObjectChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let object_removed =
ObjectRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let stream_added =
StreamAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let stream_changed =
StreamChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let stream_removed =
StreamRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let object_added_box = source_box.clone();
let object_removed_box = source_box.clone();
let object_changed_box = source_box.clone();
let stream_added_box = source_box.clone();
let stream_removed_box = source_box.clone();
let stream_changed_box = source_box.clone();
let res = conn.add_match(object_added, move |ir: ObjectAdded, _, _| {
object_added_handler::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
ObjectAdded,
>(object_added_box.clone(), ir, dummy_name)
});
if res.is_err() {
ERROR!("fail on source add event", ErrorLevel::PartialBreakage);
return conn;
}
let res = conn.add_match(object_changed, move |ir: ObjectChanged, _, _| {
object_changed_handler::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
ObjectChanged,
>(object_changed_box.clone(), ir, get_default_name_function)
});
if res.is_err() {
ERROR!("fail on source change event", ErrorLevel::PartialBreakage);
return conn;
}
let res = conn.add_match(object_removed, move |ir: ObjectRemoved, _, _| {
object_removed_handler::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
ObjectRemoved,
>(object_removed_box.clone(), ir, dummy_name)
});
if res.is_err() {
ERROR!("fail on source remove event", ErrorLevel::PartialBreakage);
return conn;
}
let res = conn.add_match(stream_added, move |ir: StreamAdded, _, _| {
audio_stream_added_handler::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
StreamAdded,
>(stream_added_box.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on output stream add event",
ErrorLevel::PartialBreakage
);
return conn;
}
let res = conn.add_match(stream_changed, move |ir: StreamChanged, _, _| {
audio_stream_changed_handler::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
StreamChanged,
>(stream_changed_box.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on output stream change event",
ErrorLevel::PartialBreakage
);
return conn;
}
let res = conn.add_match(stream_removed, move |ir: StreamRemoved, _, _| {
audio_stream_removed_handler::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
StreamRemoved,
>(stream_removed_box.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on output stream remove event",
ErrorLevel::PartialBreakage
);
return conn;
}
conn
}

View file

@ -0,0 +1,6 @@
use super::audio_entry::DBusFunction;
pub const GETCARDS: DBusFunction = DBusFunction {
function: "ListCards",
error: "Failed to get list profiles",
};

View file

@ -0,0 +1,217 @@
use std::collections::HashMap;
use std::sync::RwLock;
use std::time::Duration;
use std::{cell::RefCell, sync::Arc, time::SystemTime};
use adw::prelude::{ButtonExt, CheckButtonExt, PreferencesRowExt, RangeExt};
use adw::{ActionRow, ComboRow, PreferencesGroup};
use dbus::arg::{Arg, Get};
use glib::Propagation;
use glib::{
object::{IsA, IsClass},
Object,
};
use gtk::{gio, Button, CheckButton, Label, Scale, StringList, TemplateChild};
use re_set_lib::audio::audio_structures::{TAudioObject, TAudioStreamObject};
use crate::components::base::error::ReSetError;
use crate::components::base::error_impl::ReSetErrorImpl;
use crate::components::base::list_entry::ListEntry;
use crate::components::utils::set_action_row_ellipsis;
use super::audio_functions::refresh_default_audio_object;
use super::audio_utils::audio_dbus_call;
pub type AudioEntryMap<T> = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<T>, String)>>>;
pub type AudioStreamEntryMap<T> = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<T>)>>>;
pub type AudioMap = Arc<RwLock<HashMap<String, (u32, String)>>>;
pub trait TAudioBox<AudioBoxImpl> {
fn box_imp(&self) -> &AudioBoxImpl;
}
#[allow(dead_code)]
pub trait TAudioBoxImpl<AObject, ENTRY, STREAMENTRY> {
fn audio_object_row(&self) -> &TemplateChild<ActionRow>;
fn cards_row(&self) -> &TemplateChild<ActionRow>;
fn audio_object_dropdown(&self) -> &TemplateChild<ComboRow>;
fn audio_object_mute(&self) -> &TemplateChild<Button>;
fn volume_slider(&self) -> &TemplateChild<Scale>;
fn volume_percentage(&self) -> &TemplateChild<Label>;
fn audio_objects(&self) -> &TemplateChild<gtk::Box>;
fn audio_object_stream_button(&self) -> &TemplateChild<ActionRow>;
fn audio_object_streams(&self) -> &TemplateChild<gtk::Box>;
fn cards_button(&self) -> &TemplateChild<ActionRow>;
fn cards(&self) -> &TemplateChild<PreferencesGroup>;
fn error(&self) -> &TemplateChild<ReSetError>;
fn default_check_button(&self) -> Arc<CheckButton>;
fn default_audio_object(&self) -> Arc<RefCell<AObject>>;
fn audio_object_list(&self) -> &AudioEntryMap<ENTRY>;
fn audio_object_stream_list(&self) -> &AudioStreamEntryMap<STREAMENTRY>;
fn model_list(&self) -> Arc<RwLock<StringList>>;
fn model_index(&self) -> Arc<RwLock<u32>>;
fn source_map(&self) -> &AudioMap;
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>>;
fn icons(&self) -> &AudioIcons;
}
pub trait TAudioEntry<TAudioEntryImpl>: IsClass + IsA<glib::Object> {
fn entry_imp(&self) -> &TAudioEntryImpl;
}
pub trait TAudioEntryImpl<AudioObject: TAudioObject> {
fn name(&self) -> &TemplateChild<ActionRow>;
fn selected_audio_object(&self) -> &TemplateChild<CheckButton>;
fn mute(&self) -> &TemplateChild<Button>;
fn volume_slider(&self) -> &TemplateChild<Scale>;
fn volume_percentage(&self) -> &TemplateChild<Label>;
fn audio_object(&self) -> Arc<RefCell<AudioObject>>;
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>>;
fn set_volume_fn(&self) -> &'static DBusFunction;
fn set_audio_object_fn(&self) -> &'static DBusFunction;
fn set_mute_fn(&self) -> &'static DBusFunction;
fn icons(&self) -> &AudioIcons;
}
pub trait TAudioStream<TAudioStreamImpl>: IsClass + IsA<glib::Object> {
fn entry_imp(&self) -> &TAudioStreamImpl;
}
pub trait TAudioStreamImpl<AudioObject: TAudioObject, StreamObject: TAudioStreamObject> {
fn audio_object_selection(&self) -> &TemplateChild<ComboRow>;
fn audio_object_mute(&self) -> &TemplateChild<Button>;
fn volume_slider(&self) -> &TemplateChild<Scale>;
fn volume_percentage(&self) -> &TemplateChild<Label>;
fn stream_object(&self) -> Arc<RefCell<StreamObject>>;
fn associated_audio_object(&self) -> Arc<RefCell<(u32, String)>>;
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>>;
fn set_volume_fn(&self) -> &'static DBusFunction;
fn set_audio_object_fn(&self) -> &'static DBusFunction;
fn set_mute_fn(&self) -> &'static DBusFunction;
fn icons(&self) -> &AudioIcons;
}
pub struct AudioIcons {
pub muted: &'static str,
pub active: &'static str,
}
pub struct DBusFunction {
pub function: &'static str,
pub error: &'static str,
}
pub fn new_entry<
AudioObject: TAudioObject + Arg + for<'z> Get<'z> + Send + Sync + 'static,
StreamObject: TAudioStreamObject + Arg + for<'z> Get<'z> + Send + Sync + 'static,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
is_default: bool,
check_group: Arc<CheckButton>,
audio_object: AudioObject,
reset_box: Arc<AudioBox>,
) -> Arc<AudioEntry> {
let obj: Arc<AudioEntry> = Arc::new(Object::builder().build());
// FUTURE TODO: use event callback for progress bar -> this is the "im speaking" indicator
{
let imp = obj.entry_imp();
let slider_obj_ref = obj.clone();
let mute_obj_ref = obj.clone();
imp.name().set_title(audio_object.alias().clone().as_str());
let name = Arc::new(audio_object.name().clone());
let volume = audio_object.volume();
let volume = volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
let output_box_slider = reset_box.clone();
let output_box_ref = reset_box.clone();
imp.volume_percentage().set_text(&percentage);
imp.volume_slider().set_value(*volume as f64);
imp.audio_object().replace(audio_object);
imp.volume_slider()
.connect_change_value(move |_, _, value| {
let imp = slider_obj_ref.entry_imp();
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.volume_percentage().set_text(&percentage);
let sink = imp.audio_object();
let sink = sink.borrow();
let index = sink.index();
let channels = sink.channels();
{
let time = imp.volume_time_stamp();
let mut time = time.borrow_mut();
if time.is_some()
&& time.unwrap().elapsed().unwrap() < Duration::from_millis(50)
{
return Propagation::Proceed;
}
*time = Some(SystemTime::now());
}
audio_dbus_call::<AudioBox, (), (u32, u16, u32)>(
output_box_slider.clone(),
(index, channels, value as u32),
imp.set_volume_fn(),
);
Propagation::Proceed
});
imp.selected_audio_object().set_group(Some(&*check_group));
if is_default {
imp.selected_audio_object().set_active(true);
} else {
imp.selected_audio_object().set_active(false);
}
let audio_object_fn = imp.set_audio_object_fn();
imp.selected_audio_object().connect_toggled(move |button| {
let output_box_ref = reset_box.clone();
if button.is_active() {
let name = name.clone();
gio::spawn_blocking(move || {
let result = audio_dbus_call::<AudioBox, (AudioObject,), (&String,)>(
output_box_ref.clone(),
(&name,),
audio_object_fn,
);
if result.is_none() {
return;
}
refresh_default_audio_object::<
AudioObject,
StreamObject,
AudioEntry,
AudioEntryImpl,
AudioStream,
AudioStreamImpl,
AudioBox,
AudioBoxImpl,
>(result.unwrap().0, output_box_ref, true);
});
}
});
imp.mute().connect_clicked(move |_| {
let imp = mute_obj_ref.entry_imp();
let audio_object = imp.audio_object().clone();
let mut audio_object = audio_object.borrow_mut();
audio_object.toggle_muted();
let icons = imp.icons();
if audio_object.muted() {
imp.mute().set_icon_name(icons.muted);
} else {
imp.mute().set_icon_name(icons.active);
}
audio_dbus_call::<AudioBox, (), (u32, bool)>(
output_box_ref.clone(),
(audio_object.index(), audio_object.muted()),
imp.set_mute_fn(),
);
});
set_action_row_ellipsis(imp.name().get());
}
obj
}

View file

@ -0,0 +1,242 @@
use std::{
sync::Arc,
time::{Duration, SystemTime},
};
use adw::{prelude::ComboRowExt, prelude::PreferencesRowExt};
use glib::{object::Cast, Object, Propagation};
use gtk::{
prelude::{ButtonExt, CheckButtonExt, RangeExt},
StringObject,
};
use re_set_lib::audio::audio_structures::{TAudioObject, TAudioStreamObject};
use crate::components::{
base::error_impl::ReSetErrorImpl,
utils::{create_dropdown_label_factory, set_combo_row_ellipsis},
};
use super::{
audio_entry::{
TAudioBox, TAudioBoxImpl, TAudioEntry, TAudioEntryImpl, TAudioStream, TAudioStreamImpl,
},
audio_utils::audio_dbus_call,
};
pub fn refresh_default_audio_object<
AudioObject: TAudioObject + Send + Sync + 'static,
StreamObject: TAudioStreamObject + Send + Sync + 'static,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + Send + Sync + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
new_audio_object: AudioObject,
reset_box: Arc<AudioBox>,
entry: bool,
) {
let volume = *new_audio_object.volume().first().unwrap_or(&0_u32);
let fraction = (volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = reset_box.box_imp();
if !entry {
let list = imp.audio_object_list();
let list = list.read().unwrap();
let entry = list.get(&new_audio_object.index());
if entry.is_none() {
return;
}
let entry_imp = entry.unwrap().1.entry_imp();
entry_imp.selected_audio_object().set_active(true);
} else {
let index = imp.model_index();
let index = index.read().unwrap();
let model_list = imp.model_list();
let model_list = model_list.read().unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(new_audio_object.alias().clone().into()) {
imp.audio_object_dropdown().set_selected(entry);
break;
}
}
}
imp.volume_percentage().set_text(&percentage);
imp.volume_slider().set_value(volume as f64);
let icons = imp.icons();
if new_audio_object.muted() {
imp.audio_object_mute().set_icon_name(icons.muted);
} else {
imp.audio_object_mute().set_icon_name(icons.active);
}
imp.default_audio_object().replace(new_audio_object);
});
});
}
pub fn new_stream_entry<
AudioObject: TAudioObject + Send + Sync + 'static,
StreamObject: TAudioStreamObject + Send + Sync + 'static,
AudioEntry: TAudioEntry<AudioEntryImpl>,
AudioEntryImpl: TAudioEntryImpl<AudioObject>,
AudioStream: TAudioStream<AudioStreamImpl>,
AudioStreamImpl: TAudioStreamImpl<AudioObject, StreamObject>,
AudioBox: TAudioBox<AudioBoxImpl> + ReSetErrorImpl + Send + Sync + 'static,
AudioBoxImpl: TAudioBoxImpl<AudioObject, AudioEntry, AudioStream>,
>(
audio_box: Arc<AudioBox>,
stream: StreamObject,
) -> Arc<AudioStream> {
let obj: Arc<AudioStream> = Arc::new(Object::builder().build());
// FUTURE TODO: use event callback for progress bar -> this is the "im speaking" indicator
let output_box_mute_ref = audio_box.clone();
let output_box_volume_ref = audio_box.clone();
let output_box_sink_ref = audio_box.clone();
let entry_mute_ref = obj.clone();
let entry_volume_ref = obj.clone();
let entry_sink_ref = obj.clone();
{
let index = stream.audio_object_index();
let box_imp = audio_box.box_imp();
let imp = obj.entry_imp();
let icons = box_imp.icons();
if stream.muted() {
imp.audio_object_mute().set_icon_name(icons.muted);
} else {
imp.audio_object_mute().set_icon_name(icons.active);
}
let name = stream.application_name().clone() + ": " + stream.name().as_str();
imp.audio_object_selection().set_title(name.as_str());
imp.audio_object_selection()
.set_factory(Some(&create_dropdown_label_factory()));
set_combo_row_ellipsis(imp.audio_object_selection().get());
let volume = stream.volume();
let volume = volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.volume_percentage().set_text(&percentage);
imp.volume_slider().set_value(*volume as f64);
imp.stream_object().replace(stream);
{
let sink = box_imp.default_audio_object();
let sink = sink.borrow();
imp.associated_audio_object()
.replace((sink.index(), sink.name().clone()));
}
imp.volume_slider()
.connect_change_value(move |_, _, value| {
let imp = entry_volume_ref.entry_imp();
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.volume_percentage().set_text(&percentage);
let stream = imp.stream_object();
let mut stream_opt = stream.try_borrow();
while stream_opt.is_err() {
stream_opt = stream.try_borrow();
}
let stream = stream_opt.unwrap();
let index = stream.index();
let channels = stream.channels();
{
let mut time = imp.volume_time_stamp().borrow_mut();
if time.is_some()
&& time.unwrap().elapsed().unwrap() < Duration::from_millis(50)
{
return Propagation::Proceed;
}
*time = Some(SystemTime::now());
}
audio_dbus_call::<AudioBox, (), (u32, u16, u32)>(
output_box_volume_ref.clone(),
(index, channels, value as u32),
imp.set_volume_fn(),
);
Propagation::Proceed
});
{
let list = box_imp.model_list();
let list = list.read().unwrap();
imp.audio_object_selection().set_model(Some(&*list));
let sink_list = box_imp.audio_object_list().read().unwrap();
let name = sink_list.get(&index);
let index = box_imp.model_index();
let index = index.read().unwrap();
if let Some(name) = name {
for entry in 0..*index {
if list.string(entry) == Some(name.2.clone().into()) {
imp.audio_object_selection().set_selected(entry);
break;
}
}
} else {
let name = box_imp.default_audio_object();
let mut name_opt = name.try_borrow();
while name_opt.is_err() {
name_opt = name.try_borrow();
}
let name = name_opt.unwrap();
for entry in 0..*index {
if list.string(entry) == Some(name.alias().into()) {
imp.audio_object_selection().set_selected(entry);
break;
}
}
}
}
imp.audio_object_selection()
.connect_selected_notify(move |dropdown| {
let imp = entry_sink_ref.entry_imp();
let box_imp = output_box_sink_ref.box_imp();
let selected = dropdown.selected_item();
if selected.is_none() {
return;
}
let selected = selected.unwrap();
let selected = selected.downcast_ref::<StringObject>().unwrap();
let selected = selected.string().to_string();
let sink = box_imp.source_map().read().unwrap();
let sink = sink.get(&selected);
if sink.is_none() {
return;
}
let stream = imp.stream_object();
let mut stream_opt = stream.try_borrow();
while stream_opt.is_err() {
stream_opt = stream.try_borrow();
}
let stream = stream_opt.unwrap();
let sink = sink.unwrap().0;
audio_dbus_call::<AudioBox, (), (u32, u32)>(
output_box_sink_ref.clone(),
(stream.index(), sink),
imp.set_audio_object_fn(),
);
});
imp.audio_object_mute().connect_clicked(move |_| {
let imp = entry_mute_ref.entry_imp();
let stream = imp.stream_object().clone();
let mut stream_opt = stream.try_borrow_mut();
while stream_opt.is_err() {
stream_opt = stream.try_borrow_mut();
}
let mut stream = stream_opt.unwrap();
stream.toggle_muted();
let icons = imp.icons();
let muted = stream.muted();
if muted {
imp.audio_object_mute().set_icon_name(icons.muted);
} else {
imp.audio_object_mute().set_icon_name(icons.active);
}
audio_dbus_call::<AudioBox, (), (u32, bool)>(
output_box_mute_ref.clone(),
(stream.index(), muted),
imp.set_mute_fn(),
);
});
}
obj
}

View file

@ -0,0 +1,34 @@
use std::{sync::Arc, time::Duration};
use dbus::{
arg::{AppendAll, ReadAll},
blocking::Connection,
Error,
};
use crate::components::{
base::error_impl::{show_error, ReSetErrorImpl},
utils::{AUDIO, BASE, DBUS_PATH},
};
use super::audio_entry::DBusFunction;
pub fn audio_dbus_call<B, O, I>(
source_box: Arc<B>,
args: I,
function: &'static DBusFunction,
) -> Option<O>
where
O: ReadAll,
I: AppendAll,
B: ReSetErrorImpl + 'static,
{
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<O, Error> = proxy.method_call(AUDIO, function.function, args);
if res.is_err() {
show_error::<B>(source_box.clone(), function.error);
return None;
}
Some(res.unwrap())
}

View file

@ -0,0 +1,7 @@
pub mod output_stream_entry;
pub mod output_stream_entry_impl;
pub mod source_box;
pub mod source_box_impl;
mod source_const;
pub mod source_entry;
pub mod source_entry_impl;

View file

@ -0,0 +1,40 @@
use std::sync::Arc;
use crate::components::audio::audio_entry::TAudioStream;
use crate::components::audio::audio_functions::new_stream_entry;
use glib::subclass::types::ObjectSubclassIsExt;
use re_set_lib::audio::audio_structures::{OutputStream, Source};
use super::output_stream_entry_impl;
use super::source_box::SourceBox;
use super::source_entry::SourceEntry;
glib::wrapper! {
pub struct OutputStreamEntry(ObjectSubclass<output_stream_entry_impl::OutputStreamEntry>)
@extends adw::PreferencesGroup, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for OutputStreamEntry {}
unsafe impl Sync for OutputStreamEntry {}
impl TAudioStream<super::output_stream_entry_impl::OutputStreamEntry> for OutputStreamEntry {
fn entry_imp(&self) -> &super::output_stream_entry_impl::OutputStreamEntry {
self.imp()
}
}
impl OutputStreamEntry {
pub fn new(source_box: Arc<SourceBox>, stream: OutputStream) -> Arc<Self> {
new_stream_entry::<
Source,
OutputStream,
SourceEntry,
super::source_entry_impl::SourceEntry,
OutputStreamEntry,
super::output_stream_entry_impl::OutputStreamEntry,
SourceBox,
super::source_box_impl::SourceBox,
>(source_box, stream)
}
}

View file

@ -0,0 +1,97 @@
use adw::subclass::prelude::PreferencesGroupImpl;
use adw::{ComboRow, PreferencesGroup};
use re_set_lib::audio::audio_structures::{OutputStream, Source};
use std::cell::RefCell;
use std::sync::Arc;
use std::time::SystemTime;
use crate::components::audio::audio_entry::{AudioIcons, TAudioStreamImpl};
use crate::components::audio::input::output_stream_entry;
use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, Label, Scale};
use super::source_const::{ICONS, SETSTREAMMUTE, SETSTREAMOBJECT, SETSTREAMVOLUME};
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetOutputStreamEntry.ui")]
pub struct OutputStreamEntry {
#[template_child]
pub reset_source_selection: TemplateChild<ComboRow>,
#[template_child]
pub reset_source_mute: TemplateChild<Button>,
#[template_child]
pub reset_volume_slider: TemplateChild<Scale>,
#[template_child]
pub reset_volume_percentage: TemplateChild<Label>,
pub stream: Arc<RefCell<OutputStream>>,
pub associated_source: Arc<RefCell<(u32, String)>>,
pub volume_time_stamp: RefCell<Option<SystemTime>>,
}
#[glib::object_subclass]
impl ObjectSubclass for OutputStreamEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetOutputStreamEntry";
type Type = output_stream_entry::OutputStreamEntry;
type ParentType = PreferencesGroup;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl PreferencesGroupImpl for OutputStreamEntry {}
impl ObjectImpl for OutputStreamEntry {}
impl WidgetImpl for OutputStreamEntry {}
impl TAudioStreamImpl<Source, OutputStream> for OutputStreamEntry {
fn audio_object_selection(&self) -> &TemplateChild<ComboRow> {
&self.reset_source_selection
}
fn audio_object_mute(&self) -> &TemplateChild<Button> {
&self.reset_source_mute
}
fn volume_slider(&self) -> &TemplateChild<Scale> {
&self.reset_volume_slider
}
fn volume_percentage(&self) -> &TemplateChild<Label> {
&self.reset_volume_percentage
}
fn stream_object(&self) -> Arc<RefCell<OutputStream>> {
self.stream.clone()
}
fn associated_audio_object(&self) -> Arc<RefCell<(u32, String)>> {
self.associated_source.clone()
}
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> {
&self.volume_time_stamp
}
fn set_volume_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction {
&SETSTREAMVOLUME
}
fn set_audio_object_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction {
&SETSTREAMOBJECT
}
fn set_mute_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction {
&SETSTREAMMUTE
}
fn icons(&self) -> &AudioIcons {
&ICONS
}
}

View file

@ -0,0 +1,114 @@
use re_set_lib::audio::audio_structures::{OutputStream, Source};
use re_set_lib::signals::{
OutputStreamAdded, OutputStreamChanged, OutputStreamRemoved, SourceAdded, SourceChanged,
SourceRemoved,
};
use std::sync::Arc;
use adw::glib::Object;
use dbus::blocking::Connection;
use glib::subclass::prelude::ObjectSubclassIsExt;
use crate::components::audio::audio_box_handlers::populate_audio_objects;
use crate::components::audio::audio_box_utils::{
setup_audio_box_callbacks, start_audio_box_listener,
};
use crate::components::audio::audio_entry::TAudioBox;
use crate::components::audio::input::source_box_impl;
use crate::components::base::error::{self};
use crate::components::base::error_impl::ReSetErrorImpl;
use super::output_stream_entry::OutputStreamEntry;
use super::source_const::{
DUMMY, GETDEFAULT, GETDEFAULTNAME, GETOBJECTS, GETSTREAMS, SETDEFAULT, SETMUTE, SETVOLUME
};
use super::source_entry::SourceEntry;
glib::wrapper! {
pub struct SourceBox(ObjectSubclass<source_box_impl::SourceBox>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for SourceBox {}
unsafe impl Sync for SourceBox {}
impl ReSetErrorImpl for SourceBox {
fn error(&self) -> &gtk::subclass::prelude::TemplateChild<error::ReSetError> {
&self.imp().error
}
}
impl TAudioBox<super::source_box_impl::SourceBox> for SourceBox {
fn box_imp(&self) -> &super::source_box_impl::SourceBox {
self.imp()
}
}
impl SourceBox {
pub fn new() -> Self {
let mut obj: Self = Object::builder().build();
setup_audio_box_callbacks::<
Source,
OutputStream,
SourceEntry,
super::source_entry_impl::SourceEntry,
OutputStreamEntry,
super::output_stream_entry_impl::OutputStreamEntry,
SourceBox,
super::source_box_impl::SourceBox,
>(&mut obj);
{
let imp = obj.imp();
let mut model_index = imp.reset_model_index.write().unwrap();
*model_index = 0;
}
obj
}
}
impl Default for SourceBox {
fn default() -> Self {
Self::new()
}
}
pub fn populate_sources(source_box: Arc<SourceBox>) {
populate_audio_objects::<
Source,
OutputStream,
SourceEntry,
super::source_entry_impl::SourceEntry,
OutputStreamEntry,
super::output_stream_entry_impl::OutputStreamEntry,
SourceBox,
super::source_box_impl::SourceBox,
>(
source_box,
&GETOBJECTS,
&GETDEFAULT,
&SETDEFAULT,
&GETSTREAMS,
&SETVOLUME,
&SETMUTE,
);
}
pub fn start_source_box_listener(conn: Connection, source_box: Arc<SourceBox>) -> Connection {
start_audio_box_listener::<
Source,
OutputStream,
SourceEntry,
super::source_entry_impl::SourceEntry,
OutputStreamEntry,
super::output_stream_entry_impl::OutputStreamEntry,
SourceBox,
super::source_box_impl::SourceBox,
SourceAdded,
SourceChanged,
SourceRemoved,
OutputStreamAdded,
OutputStreamChanged,
OutputStreamRemoved,
>(conn, source_box, &GETDEFAULTNAME, DUMMY)
}

View file

@ -0,0 +1,181 @@
use adw::{ActionRow, ComboRow, PreferencesGroup};
use re_set_lib::audio::audio_structures::Source;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::SystemTime;
use crate::components::audio::audio_entry::{AudioIcons, TAudioBoxImpl};
use crate::components::audio::input::source_box;
use crate::components::base::error::ReSetError;
use crate::components::base::list_entry::ListEntry;
use gtk::subclass::prelude::*;
use gtk::{prelude::*, Button, Label, Scale};
use gtk::{CheckButton, CompositeTemplate, StringList};
use super::output_stream_entry::OutputStreamEntry;
use super::source_const::ICONS;
use super::source_entry::SourceEntry;
type SourceEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SourceEntry>, String)>>>;
type OutputStreamEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<OutputStreamEntry>)>>>;
// the key is the alias, the first value u32 is the index of the source, the second is the technical name
type SourceMap = Arc<RwLock<HashMap<String, (u32, String)>>>;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetAudioInput.ui")]
pub struct SourceBox {
#[template_child]
pub reset_source_row: TemplateChild<ActionRow>,
#[template_child]
pub reset_cards_row: TemplateChild<ActionRow>,
#[template_child]
pub reset_source_dropdown: TemplateChild<ComboRow>,
#[template_child]
pub reset_source_mute: TemplateChild<Button>,
#[template_child]
pub reset_volume_slider: TemplateChild<Scale>,
#[template_child]
pub reset_volume_percentage: TemplateChild<Label>,
#[template_child]
pub reset_sources: TemplateChild<gtk::Box>,
#[template_child]
pub reset_output_stream_button: TemplateChild<ActionRow>,
#[template_child]
pub reset_output_streams: TemplateChild<gtk::Box>,
#[template_child]
pub reset_input_cards_back_button: TemplateChild<ActionRow>,
#[template_child]
pub reset_cards: TemplateChild<PreferencesGroup>,
#[template_child]
pub error: TemplateChild<ReSetError>,
pub reset_default_check_button: Arc<CheckButton>,
pub reset_default_source: Arc<RefCell<Source>>,
pub reset_source_list: SourceEntryMap,
pub reset_output_stream_list: OutputStreamEntryMap,
pub reset_model_list: Arc<RwLock<StringList>>,
pub reset_model_index: Arc<RwLock<u32>>,
pub reset_source_map: SourceMap,
pub volume_time_stamp: RefCell<Option<SystemTime>>,
}
#[glib::object_subclass]
impl ObjectSubclass for SourceBox {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetAudioInput";
type Type = source_box::SourceBox;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
OutputStreamEntry::ensure_type();
SourceEntry::ensure_type();
ListEntry::ensure_type();
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl BoxImpl for SourceBox {}
impl ObjectImpl for SourceBox {}
impl ListBoxRowImpl for SourceBox {}
impl WidgetImpl for SourceBox {}
impl WindowImpl for SourceBox {}
impl ApplicationWindowImpl for SourceBox {}
impl TAudioBoxImpl<Source, SourceEntry, OutputStreamEntry> for SourceBox {
fn audio_object_row(&self) -> &TemplateChild<ActionRow> {
&self.reset_source_row
}
fn cards_row(&self) -> &TemplateChild<ActionRow> {
&self.reset_cards_row
}
fn audio_object_dropdown(&self) -> &TemplateChild<ComboRow> {
&self.reset_source_dropdown
}
fn audio_object_mute(&self) -> &TemplateChild<Button> {
&self.reset_source_mute
}
fn volume_slider(&self) -> &TemplateChild<Scale> {
&self.reset_volume_slider
}
fn volume_percentage(&self) -> &TemplateChild<Label> {
&self.reset_volume_percentage
}
fn audio_objects(&self) -> &TemplateChild<gtk::Box> {
&self.reset_sources
}
fn audio_object_stream_button(&self) -> &TemplateChild<ActionRow> {
&self.reset_output_stream_button
}
fn audio_object_streams(&self) -> &TemplateChild<gtk::Box> {
&self.reset_output_streams
}
fn cards_button(&self) -> &TemplateChild<ActionRow> {
&self.reset_input_cards_back_button
}
fn cards(&self) -> &TemplateChild<PreferencesGroup> {
&self.reset_cards
}
fn error(&self) -> &TemplateChild<ReSetError> {
&self.error
}
fn default_check_button(&self) -> Arc<CheckButton> {
self.reset_default_check_button.clone()
}
fn default_audio_object(&self) -> Arc<RefCell<Source>> {
self.reset_default_source.clone()
}
fn audio_object_list(
&self,
) -> &crate::components::audio::audio_entry::AudioEntryMap<SourceEntry> {
&self.reset_source_list
}
fn audio_object_stream_list(
&self,
) -> &crate::components::audio::audio_entry::AudioStreamEntryMap<OutputStreamEntry> {
&self.reset_output_stream_list
}
fn model_list(&self) -> Arc<RwLock<StringList>> {
self.reset_model_list.clone()
}
fn model_index(&self) -> Arc<RwLock<u32>> {
self.reset_model_index.clone()
}
fn source_map(&self) -> &crate::components::audio::audio_entry::AudioMap {
&self.reset_source_map
}
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> {
&self.volume_time_stamp
}
fn icons(&self) -> &AudioIcons {
&ICONS
}
}

View file

@ -0,0 +1,58 @@
use crate::components::audio::audio_entry::{AudioIcons, DBusFunction};
pub const ICONS: AudioIcons = AudioIcons {
muted: "microphone-disabled-symbolic",
active: "audio-input-microphone-symbolic",
};
pub const SETVOLUME: DBusFunction = DBusFunction {
function: "SetSourceVolume",
error: "Failed to set source volume",
};
pub const SETMUTE: DBusFunction = DBusFunction {
function: "SetSourceMute",
error: "Failed to mute source",
};
pub const SETDEFAULT: DBusFunction = DBusFunction {
function: "SetDefaultSource",
error: "Failed to set default source",
};
pub const GETDEFAULT: DBusFunction = DBusFunction {
function: "GetDefaultSource",
error: "Failed to get default source",
};
pub const GETDEFAULTNAME: DBusFunction = DBusFunction {
function: "GetDefaultSourceName",
error: "Failed to get default source name",
};
pub const GETOBJECTS: DBusFunction = DBusFunction {
function: "ListSources",
error: "Failed to list sources",
};
pub const GETSTREAMS: DBusFunction = DBusFunction {
function: "ListOutputStreams",
error: "Failed to list output streams",
};
pub const SETSTREAMVOLUME: DBusFunction = DBusFunction {
function: "SetOutputStreamVolume",
error: "Failed to set output stream volume",
};
pub const SETSTREAMMUTE: DBusFunction = DBusFunction {
function: "SetOutputStreamMute",
error: "Failed to mute output stream",
};
pub const SETSTREAMOBJECT: DBusFunction = DBusFunction {
function: "SetSourceOfOutputStream",
error: "Failed to set source of output stream",
};
pub const DUMMY: &str = "Monitor of Dummy Output";

View file

@ -0,0 +1,45 @@
use std::sync::Arc;
use crate::components::audio::audio_entry::{new_entry, TAudioEntry};
use glib::subclass::types::ObjectSubclassIsExt;
use gtk::CheckButton;
use re_set_lib::audio::audio_structures::{OutputStream, Source};
use super::output_stream_entry::OutputStreamEntry;
use super::source_box::SourceBox;
use super::source_entry_impl;
glib::wrapper! {
pub struct SourceEntry(ObjectSubclass<source_entry_impl::SourceEntry>)
@extends adw::PreferencesGroup, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for SourceEntry {}
unsafe impl Sync for SourceEntry {}
impl TAudioEntry<super::source_entry_impl::SourceEntry> for SourceEntry {
fn entry_imp(&self) -> &super::source_entry_impl::SourceEntry {
self.imp()
}
}
impl SourceEntry {
pub fn new(
is_default: bool,
check_group: Arc<CheckButton>,
source: Source,
input_box: Arc<SourceBox>,
) -> Arc<Self> {
new_entry::<
Source,
OutputStream,
SourceEntry,
super::source_entry_impl::SourceEntry,
OutputStreamEntry,
super::output_stream_entry_impl::OutputStreamEntry,
SourceBox,
super::source_box_impl::SourceBox,
>(is_default, check_group, source, input_box)
}
}

View file

@ -0,0 +1,99 @@
use adw::subclass::prelude::PreferencesGroupImpl;
use adw::{ActionRow, PreferencesGroup};
use re_set_lib::audio::audio_structures::Source;
use std::cell::RefCell;
use std::sync::Arc;
use std::time::SystemTime;
use gtk::subclass::prelude::*;
use gtk::{Button, CheckButton, CompositeTemplate, Label, Scale};
use crate::components::audio::audio_entry::{AudioIcons, DBusFunction, TAudioEntryImpl};
use super::source_const::{ICONS, SETDEFAULT, SETMUTE, SETVOLUME};
use super::source_entry;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetSourceEntry.ui")]
pub struct SourceEntry {
#[template_child]
pub reset_source_name: TemplateChild<ActionRow>,
#[template_child]
pub reset_selected_source: TemplateChild<CheckButton>,
#[template_child]
pub reset_source_mute: TemplateChild<Button>,
#[template_child]
pub reset_volume_slider: TemplateChild<Scale>,
#[template_child]
pub reset_volume_percentage: TemplateChild<Label>,
pub source: Arc<RefCell<Source>>,
pub volume_time_stamp: RefCell<Option<SystemTime>>,
}
#[glib::object_subclass]
impl ObjectSubclass for SourceEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetSourceEntry";
type Type = source_entry::SourceEntry;
type ParentType = PreferencesGroup;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl PreferencesGroupImpl for SourceEntry {}
impl ObjectImpl for SourceEntry {}
impl WidgetImpl for SourceEntry {}
impl TAudioEntryImpl<Source> for SourceEntry {
fn name(&self) -> &TemplateChild<ActionRow> {
&self.reset_source_name
}
fn selected_audio_object(&self) -> &TemplateChild<CheckButton> {
&self.reset_selected_source
}
fn mute(&self) -> &TemplateChild<Button> {
&self.reset_source_mute
}
fn volume_slider(&self) -> &TemplateChild<Scale> {
&self.reset_volume_slider
}
fn volume_percentage(&self) -> &TemplateChild<Label> {
&self.reset_volume_percentage
}
fn audio_object(&self) -> Arc<RefCell<Source>> {
self.source.clone()
}
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> {
&self.volume_time_stamp
}
fn set_volume_fn(&self) -> &'static DBusFunction {
&SETVOLUME
}
fn set_audio_object_fn(&self) -> &'static DBusFunction {
&SETDEFAULT
}
fn set_mute_fn(&self) -> &'static DBusFunction {
&SETMUTE
}
fn icons(&self) -> &AudioIcons {
&ICONS
}
}

View file

@ -1,5 +1,8 @@
#![allow(non_snake_case)]
pub mod audioSource;
pub mod audioBox;
pub mod audioBoxImpl;
pub mod audioSourceImpl;
mod audio_box_handlers;
mod audio_box_utils;
mod audio_const;
pub mod audio_entry;
pub mod audio_functions;
mod audio_utils;
pub mod input;
pub mod output;

View file

@ -0,0 +1,39 @@
use std::sync::Arc;
use crate::components::audio::audio_entry::TAudioStream;
use crate::components::audio::audio_functions::new_stream_entry;
use glib::subclass::types::ObjectSubclassIsExt;
use re_set_lib::audio::audio_structures::{InputStream, Sink};
use super::sink_box::SinkBox;
use super::sink_entry::SinkEntry;
glib::wrapper! {
pub struct InputStreamEntry(ObjectSubclass<super::input_stream_entry_impl::InputStreamEntry>)
@extends adw::PreferencesGroup, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for InputStreamEntry {}
unsafe impl Sync for InputStreamEntry {}
impl TAudioStream<super::input_stream_entry_impl::InputStreamEntry> for InputStreamEntry {
fn entry_imp(&self) -> &super::input_stream_entry_impl::InputStreamEntry {
self.imp()
}
}
impl InputStreamEntry {
pub fn new(source_box: Arc<SinkBox>, stream: InputStream) -> Arc<Self> {
new_stream_entry::<
Sink,
InputStream,
SinkEntry,
super::sink_entry_impl::SinkEntry,
InputStreamEntry,
super::input_stream_entry_impl::InputStreamEntry,
SinkBox,
super::sink_box_impl::SinkBox,
>(source_box, stream)
}
}

View file

@ -0,0 +1,98 @@
use adw::subclass::prelude::PreferencesGroupImpl;
use adw::{ComboRow, PreferencesGroup};
use re_set_lib::audio::audio_structures::{InputStream, Sink};
use std::cell::RefCell;
use std::sync::Arc;
use std::time::SystemTime;
use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, Label, Scale};
use crate::components::audio::audio_entry::{AudioIcons, TAudioStreamImpl};
use super::input_stream_entry;
use super::sink_const::{ICONS, SETSTREAMMUTE, SETSTREAMOBJECT, SETSTREAMVOLUME};
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetInputStreamEntry.ui")]
pub struct InputStreamEntry {
#[template_child]
pub reset_sink_selection: TemplateChild<ComboRow>,
#[template_child]
pub reset_sink_mute: TemplateChild<Button>,
#[template_child]
pub reset_volume_slider: TemplateChild<Scale>,
#[template_child]
pub reset_volume_percentage: TemplateChild<Label>,
pub stream: Arc<RefCell<InputStream>>,
pub associated_sink: Arc<RefCell<(u32, String)>>,
pub volume_time_stamp: RefCell<Option<SystemTime>>,
}
#[glib::object_subclass]
impl ObjectSubclass for InputStreamEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetInputStreamEntry";
type Type = input_stream_entry::InputStreamEntry;
type ParentType = PreferencesGroup;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl PreferencesGroupImpl for InputStreamEntry {}
impl ObjectImpl for InputStreamEntry {}
impl WidgetImpl for InputStreamEntry {}
impl TAudioStreamImpl<Sink, InputStream> for InputStreamEntry {
fn audio_object_selection(&self) -> &TemplateChild<ComboRow> {
&self.reset_sink_selection
}
fn audio_object_mute(&self) -> &TemplateChild<Button> {
&self.reset_sink_mute
}
fn volume_slider(&self) -> &TemplateChild<Scale> {
&self.reset_volume_slider
}
fn volume_percentage(&self) -> &TemplateChild<Label> {
&self.reset_volume_percentage
}
fn stream_object(&self) -> Arc<RefCell<InputStream>> {
self.stream.clone()
}
fn associated_audio_object(&self) -> Arc<RefCell<(u32, String)>> {
self.associated_sink.clone()
}
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> {
&self.volume_time_stamp
}
fn set_volume_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction {
&SETSTREAMVOLUME
}
fn set_audio_object_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction {
&SETSTREAMOBJECT
}
fn set_mute_fn(&self) -> &'static crate::components::audio::audio_entry::DBusFunction {
&SETSTREAMMUTE
}
fn icons(&self) -> &AudioIcons {
&ICONS
}
}

View file

@ -0,0 +1,7 @@
pub mod input_stream_entry;
pub mod input_stream_entry_impl;
pub mod sink_box;
pub mod sink_box_impl;
mod sink_const;
pub mod sink_entry;
pub mod sink_entry_impl;

View file

@ -0,0 +1,118 @@
use re_set_lib::audio::audio_structures::InputStream;
use re_set_lib::audio::audio_structures::Sink;
use re_set_lib::signals::InputStreamAdded;
use re_set_lib::signals::InputStreamChanged;
use re_set_lib::signals::InputStreamRemoved;
use re_set_lib::signals::SinkAdded;
use re_set_lib::signals::SinkChanged;
use re_set_lib::signals::SinkRemoved;
use std::sync::Arc;
use adw::glib::Object;
use dbus::blocking::Connection;
use glib::subclass::prelude::ObjectSubclassIsExt;
use crate::components::audio::audio_box_handlers::populate_audio_objects;
use crate::components::audio::audio_box_utils::setup_audio_box_callbacks;
use crate::components::audio::audio_box_utils::start_audio_box_listener;
use crate::components::audio::audio_entry::TAudioBox;
use crate::components::base::error_impl::ReSetErrorImpl;
use super::input_stream_entry::InputStreamEntry;
use super::sink_box_impl;
use super::sink_const::DUMMY;
use super::sink_const::{
GETDEFAULT, GETDEFAULTNAME, GETOBJECTS, GETSTREAMS, SETDEFAULT, SETMUTE, SETVOLUME,
};
use super::sink_entry::SinkEntry;
glib::wrapper! {
pub struct SinkBox(ObjectSubclass<sink_box_impl::SinkBox>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for SinkBox {}
unsafe impl Sync for SinkBox {}
impl ReSetErrorImpl for SinkBox {
fn error(
&self,
) -> &gtk::subclass::prelude::TemplateChild<crate::components::base::error::ReSetError> {
&self.imp().error
}
}
impl TAudioBox<super::sink_box_impl::SinkBox> for SinkBox {
fn box_imp(&self) -> &super::sink_box_impl::SinkBox {
self.imp()
}
}
impl SinkBox {
pub fn new() -> Self {
let mut obj: Self = Object::builder().build();
setup_audio_box_callbacks::<
Sink,
InputStream,
SinkEntry,
super::sink_entry_impl::SinkEntry,
InputStreamEntry,
super::input_stream_entry_impl::InputStreamEntry,
SinkBox,
super::sink_box_impl::SinkBox,
>(&mut obj);
{
let imp = obj.imp();
let mut model_index = imp.reset_model_index.write().unwrap();
*model_index = 0;
}
obj
}
}
impl Default for SinkBox {
fn default() -> Self {
Self::new()
}
}
pub fn populate_sinks(sink_box: Arc<SinkBox>) {
populate_audio_objects::<
Sink,
InputStream,
SinkEntry,
super::sink_entry_impl::SinkEntry,
InputStreamEntry,
super::input_stream_entry_impl::InputStreamEntry,
SinkBox,
super::sink_box_impl::SinkBox,
>(
sink_box,
&GETOBJECTS,
&GETDEFAULT,
&SETDEFAULT,
&GETSTREAMS,
&SETVOLUME,
&SETMUTE,
);
}
pub fn start_sink_box_listener(conn: Connection, sink_box: Arc<SinkBox>) -> Connection {
start_audio_box_listener::<
Sink,
InputStream,
SinkEntry,
super::sink_entry_impl::SinkEntry,
InputStreamEntry,
super::input_stream_entry_impl::InputStreamEntry,
SinkBox,
super::sink_box_impl::SinkBox,
SinkAdded,
SinkChanged,
SinkRemoved,
InputStreamAdded,
InputStreamChanged,
InputStreamRemoved,
>(conn, sink_box, &GETDEFAULTNAME, DUMMY)
}

View file

@ -0,0 +1,182 @@
use adw::{ActionRow, ComboRow, PreferencesGroup};
use re_set_lib::audio::audio_structures::Sink;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::SystemTime;
use crate::components::audio::audio_entry::{AudioIcons, TAudioBoxImpl};
use crate::components::audio::output::input_stream_entry::InputStreamEntry;
use crate::components::base::error::ReSetError;
use crate::components::base::list_entry::ListEntry;
use gtk::subclass::prelude::*;
use gtk::{prelude::*, Scale};
use gtk::{Box, Button, CheckButton, CompositeTemplate, Label, StringList};
use super::sink_box;
use super::sink_const::ICONS;
use super::sink_entry::SinkEntry;
type SinkEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SinkEntry>, String)>>>;
type InputStreamEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<InputStreamEntry>)>>>;
// key is model name -> alias, first u32 is the index of the sink, the second the index in the model list and the third is
// the detailed name
type SinkMap = Arc<RwLock<HashMap<String, (u32, String)>>>;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetAudioOutput.ui")]
pub struct SinkBox {
#[template_child]
pub reset_sinks_row: TemplateChild<ActionRow>,
#[template_child]
pub reset_cards_row: TemplateChild<ActionRow>,
#[template_child]
pub reset_sink_dropdown: TemplateChild<ComboRow>,
#[template_child]
pub reset_sink_mute: TemplateChild<Button>,
#[template_child]
pub reset_volume_slider: TemplateChild<Scale>,
#[template_child]
pub reset_volume_percentage: TemplateChild<Label>,
#[template_child]
pub reset_sinks: TemplateChild<Box>,
#[template_child]
pub reset_input_stream_button: TemplateChild<ActionRow>,
#[template_child]
pub reset_input_streams: TemplateChild<Box>,
#[template_child]
pub reset_input_cards_back_button: TemplateChild<ActionRow>,
#[template_child]
pub reset_cards: TemplateChild<PreferencesGroup>,
#[template_child]
pub error: TemplateChild<ReSetError>,
pub reset_default_check_button: Arc<CheckButton>,
pub reset_default_sink: Arc<RefCell<Sink>>,
pub reset_sink_list: SinkEntryMap,
pub reset_input_stream_list: InputStreamEntryMap,
pub reset_model_list: Arc<RwLock<StringList>>,
pub reset_model_index: Arc<RwLock<u32>>,
pub reset_sink_map: SinkMap,
pub volume_time_stamp: RefCell<Option<SystemTime>>,
}
#[glib::object_subclass]
impl ObjectSubclass for SinkBox {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetAudioOutput";
type Type = sink_box::SinkBox;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
InputStreamEntry::ensure_type();
SinkEntry::ensure_type();
ListEntry::ensure_type();
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl BoxImpl for SinkBox {}
impl ObjectImpl for SinkBox {}
impl ListBoxRowImpl for SinkBox {}
impl WidgetImpl for SinkBox {}
impl WindowImpl for SinkBox {}
impl ApplicationWindowImpl for SinkBox {}
impl TAudioBoxImpl<Sink, SinkEntry, InputStreamEntry> for SinkBox {
fn audio_object_row(&self) -> &TemplateChild<ActionRow> {
&self.reset_sinks_row
}
fn cards_row(&self) -> &TemplateChild<ActionRow> {
&self.reset_cards_row
}
fn audio_object_dropdown(&self) -> &TemplateChild<ComboRow> {
&self.reset_sink_dropdown
}
fn audio_object_mute(&self) -> &TemplateChild<Button> {
&self.reset_sink_mute
}
fn volume_slider(&self) -> &TemplateChild<Scale> {
&self.reset_volume_slider
}
fn volume_percentage(&self) -> &TemplateChild<Label> {
&self.reset_volume_percentage
}
fn audio_objects(&self) -> &TemplateChild<gtk::Box> {
&self.reset_sinks
}
fn audio_object_stream_button(&self) -> &TemplateChild<ActionRow> {
&self.reset_input_stream_button
}
fn audio_object_streams(&self) -> &TemplateChild<gtk::Box> {
&self.reset_input_streams
}
fn cards_button(&self) -> &TemplateChild<ActionRow> {
&self.reset_input_cards_back_button
}
fn cards(&self) -> &TemplateChild<PreferencesGroup> {
&self.reset_cards
}
fn error(&self) -> &TemplateChild<ReSetError> {
&self.error
}
fn default_check_button(&self) -> Arc<CheckButton> {
self.reset_default_check_button.clone()
}
fn default_audio_object(&self) -> Arc<RefCell<Sink>> {
self.reset_default_sink.clone()
}
fn audio_object_list(
&self,
) -> &crate::components::audio::audio_entry::AudioEntryMap<SinkEntry> {
&self.reset_sink_list
}
fn audio_object_stream_list(
&self,
) -> &crate::components::audio::audio_entry::AudioStreamEntryMap<InputStreamEntry> {
&self.reset_input_stream_list
}
fn model_list(&self) -> Arc<RwLock<StringList>> {
self.reset_model_list.clone()
}
fn model_index(&self) -> Arc<RwLock<u32>> {
self.reset_model_index.clone()
}
fn source_map(&self) -> &crate::components::audio::audio_entry::AudioMap {
&self.reset_sink_map
}
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> {
&self.volume_time_stamp
}
fn icons(&self) -> &AudioIcons {
&ICONS
}
}

View file

@ -0,0 +1,58 @@
use crate::components::audio::audio_entry::{AudioIcons, DBusFunction};
pub const ICONS: AudioIcons = AudioIcons {
muted: "audio-volume-muted-symbolic",
active: "audio-volume-high-symbolic",
};
pub const SETVOLUME: DBusFunction = DBusFunction {
function: "SetSinkVolume",
error: "Failed to set sink volume",
};
pub const SETMUTE: DBusFunction = DBusFunction {
function: "SetSinkMute",
error: "Failed to mute sink",
};
pub const SETDEFAULT: DBusFunction = DBusFunction {
function: "SetDefaultSink",
error: "Failed to set default sink",
};
pub const GETDEFAULT: DBusFunction = DBusFunction {
function: "GetDefaultSink",
error: "Failed to get default sink",
};
pub const GETDEFAULTNAME: DBusFunction = DBusFunction {
function: "GetDefaultSinkName",
error: "Failed to get default sink name",
};
pub const GETOBJECTS: DBusFunction = DBusFunction {
function: "ListSinks",
error: "Failed to list sinks",
};
pub const GETSTREAMS: DBusFunction = DBusFunction {
function: "ListInputStreams",
error: "Failed to list input streams",
};
pub const SETSTREAMVOLUME: DBusFunction = DBusFunction {
function: "SetInputStreamVolume",
error: "Failed to set input stream volume",
};
pub const SETSTREAMMUTE: DBusFunction = DBusFunction {
function: "SetInputStreamMute",
error: "Failed to mute input stream",
};
pub const SETSTREAMOBJECT: DBusFunction = DBusFunction {
function: "SetSinkOfInputStream",
error: "Failed to set sink of input stream",
};
pub const DUMMY: &str = "Dummy Input";

View file

@ -0,0 +1,45 @@
use std::sync::Arc;
use crate::components::audio::audio_entry::{new_entry, TAudioEntry};
use glib::subclass::types::ObjectSubclassIsExt;
use gtk::CheckButton;
use re_set_lib::audio::audio_structures::{InputStream, Sink};
use super::input_stream_entry::InputStreamEntry;
use super::sink_box::SinkBox;
use super::sink_entry_impl;
glib::wrapper! {
pub struct SinkEntry(ObjectSubclass<sink_entry_impl::SinkEntry>)
@extends adw::PreferencesGroup, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for SinkEntry {}
unsafe impl Sync for SinkEntry {}
impl TAudioEntry<super::sink_entry_impl::SinkEntry> for SinkEntry {
fn entry_imp(&self) -> &super::sink_entry_impl::SinkEntry {
self.imp()
}
}
impl SinkEntry {
pub fn new(
is_default: bool,
check_group: Arc<CheckButton>,
sink: Sink,
output_box: Arc<SinkBox>,
) -> Arc<Self> {
new_entry::<
Sink,
InputStream,
SinkEntry,
super::sink_entry_impl::SinkEntry,
InputStreamEntry,
super::input_stream_entry_impl::InputStreamEntry,
SinkBox,
super::sink_box_impl::SinkBox,
>(is_default, check_group, sink, output_box)
}
}

View file

@ -0,0 +1,98 @@
use adw::subclass::prelude::PreferencesGroupImpl;
use adw::{ActionRow, PreferencesGroup};
use re_set_lib::audio::audio_structures::Sink;
use std::cell::RefCell;
use std::sync::Arc;
use std::time::SystemTime;
use crate::components::audio::audio_entry::{AudioIcons, DBusFunction, TAudioEntryImpl};
use crate::components::audio::output::sink_entry;
use gtk::subclass::prelude::*;
use gtk::{Button, CheckButton, CompositeTemplate, Label, Scale};
use super::sink_const::{ICONS, SETDEFAULT, SETMUTE, SETVOLUME};
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetSinkEntry.ui")]
pub struct SinkEntry {
#[template_child]
pub reset_sink_name: TemplateChild<ActionRow>,
#[template_child]
pub reset_selected_sink: TemplateChild<CheckButton>,
#[template_child]
pub reset_sink_mute: TemplateChild<Button>,
#[template_child]
pub reset_volume_slider: TemplateChild<Scale>,
#[template_child]
pub reset_volume_percentage: TemplateChild<Label>,
pub sink: Arc<RefCell<Sink>>,
pub volume_time_stamp: RefCell<Option<SystemTime>>,
}
#[glib::object_subclass]
impl ObjectSubclass for SinkEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetSinkEntry";
type Type = sink_entry::SinkEntry;
type ParentType = PreferencesGroup;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl PreferencesGroupImpl for SinkEntry {}
impl ObjectImpl for SinkEntry {}
impl WidgetImpl for SinkEntry {}
impl TAudioEntryImpl<Sink> for SinkEntry {
fn name(&self) -> &TemplateChild<ActionRow> {
&self.reset_sink_name
}
fn selected_audio_object(&self) -> &TemplateChild<CheckButton> {
&self.reset_selected_sink
}
fn mute(&self) -> &TemplateChild<Button> {
&self.reset_sink_mute
}
fn volume_slider(&self) -> &TemplateChild<Scale> {
&self.reset_volume_slider
}
fn volume_percentage(&self) -> &TemplateChild<Label> {
&self.reset_volume_percentage
}
fn audio_object(&self) -> Arc<RefCell<Sink>> {
self.sink.clone()
}
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> {
&self.volume_time_stamp
}
fn set_volume_fn(&self) -> &'static DBusFunction {
&SETVOLUME
}
fn set_audio_object_fn(&self) -> &'static DBusFunction {
&SETDEFAULT
}
fn set_mute_fn(&self) -> &'static DBusFunction {
&SETMUTE
}
fn icons(&self) -> &AudioIcons {
&ICONS
}
}

View file

@ -0,0 +1,77 @@
use std::time::Duration;
use adw::glib::Object;
use adw::prelude::{ComboRowExt, PreferencesRowExt};
use dbus::blocking::Connection;
use dbus::Error;
use glib::clone;
use glib::prelude::Cast;
use glib::subclass::types::ObjectSubclassIsExt;
use gtk::{gio, StringList, StringObject};
use components::utils::create_dropdown_label_factory;
use re_set_lib::audio::audio_structures::Card;
use crate::components;
use crate::components::utils::{AUDIO, BASE, DBUS_PATH};
use super::card_entry_impl;
glib::wrapper! {
pub struct CardEntry(ObjectSubclass<card_entry_impl::CardEntry>)
@extends adw::ComboRow, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable, adw::PreferencesRow;
}
impl CardEntry {
pub fn new(card: Card) -> Self {
let entry: CardEntry = Object::builder().build();
{
let imp = entry.imp();
let mut map = imp.reset_card_map.borrow_mut();
entry.set_title(&card.name);
let mut index: u32 = 0;
let list = StringList::new(&[]);
for (i, profile) in (0_u32..).zip(card.profiles.iter()) {
if profile.name == card.active_profile {
index = i;
}
list.append(&profile.description);
map.insert(
profile.description.clone(),
(card.index, profile.name.clone()),
);
}
entry.set_model(Some(&list));
entry.set_selected(index);
entry.set_use_subtitle(true);
entry.connect_selected_notify(clone!(@weak imp => move |dropdown| {
let selected = dropdown.selected_item();
if selected.is_none() {
return;
}
let selected = selected.unwrap();
let selected = selected.downcast_ref::<StringObject>().unwrap();
let selected = selected.string().to_string();
let map = imp.reset_card_map.borrow();
let (device_index, profile_name) = map.get(&selected).unwrap();
set_card_profile_of_device(*device_index, profile_name.clone());
}));
entry.set_factory(Some(&create_dropdown_label_factory()));
}
entry
}
}
fn set_card_profile_of_device(device_index: u32, profile_name: String) -> bool {
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let _: Result<(), Error> = proxy.method_call(
AUDIO,
"SetCardProfileOfDevice",
(device_index, profile_name),
);
});
true
}

View file

@ -0,0 +1,53 @@
use adw::subclass::action_row::ActionRowImpl;
use adw::subclass::preferences_row::PreferencesRowImpl;
use adw::subclass::prelude::ComboRowImpl;
use adw::ComboRow;
use std::cell::RefCell;
use std::collections::HashMap;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
use super::card_entry;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetCardEntry.ui")]
pub struct CardEntry {
// first string is the alias name, the first return string is the index of the adapter and the
// second the name of the profile
pub reset_card_map: RefCell<HashMap<String, (u32, String)>>,
}
#[glib::object_subclass]
impl ObjectSubclass for CardEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetCardEntry";
type Type = card_entry::CardEntry;
type ParentType = ComboRow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ActionRowImpl for CardEntry {}
impl PreferencesRowImpl for CardEntry {}
impl ComboRowImpl for CardEntry {}
impl ObjectImpl for CardEntry {
fn constructed(&self) {}
}
impl ListBoxRowImpl for CardEntry {}
impl WidgetImpl for CardEntry {}
impl WindowImpl for CardEntry {}
impl ApplicationWindowImpl for CardEntry {}

View file

@ -0,0 +1,37 @@
use adw::glib::Object;
use glib::{clone, subclass::types::ObjectSubclassIsExt};
use gtk::{
gdk,
prelude::{ButtonExt, PopoverExt},
Editable, Popover,
};
use super::error_impl;
glib::wrapper! {
pub struct ReSetError(ObjectSubclass<error_impl::ReSetError>)
@extends Popover, gtk::Widget,
@implements Editable,gdk::Popup, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for ReSetError {}
unsafe impl Sync for ReSetError {}
impl ReSetError {
pub fn new() -> Self {
let error: ReSetError = Object::builder().build();
error
.imp()
.reset_error_button
.connect_clicked(clone!(@strong error => move |_| {
error.popdown();
}));
error
}
}
impl Default for ReSetError {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,74 @@
use std::sync::Arc;
use gtk::prelude::{ButtonExt, PopoverExt};
use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, Label, Popover};
use super::error;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetError.ui")]
pub struct ReSetError {
#[template_child]
pub reset_error_label: TemplateChild<Label>,
#[template_child]
pub reset_error_button: TemplateChild<Button>,
}
unsafe impl Send for ReSetError {}
unsafe impl Sync for ReSetError {}
#[glib::object_subclass]
impl ObjectSubclass for ReSetError {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetError";
type Type = error::ReSetError;
type ParentType = Popover;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ReSetError {
fn constructed(&self) {
self.parent_constructed();
}
}
impl WidgetImpl for ReSetError {}
impl WindowImpl for ReSetError {}
impl PopoverImpl for ReSetError {}
impl ApplicationWindowImpl for ReSetError {}
impl EditableImpl for ReSetError {}
pub fn show_error<T: ReSetErrorImpl + Send + Sync + 'static>(
parent: Arc<T>,
message: &'static str,
) {
// FUTURE TODO: Add error to log
glib::spawn_future(async move {
glib::idle_add_once(move || {
let error = parent.error();
let parent_ref = parent.clone();
let imp = error.imp();
imp.reset_error_label.set_text(message);
imp.reset_error_button.connect_clicked(move |_| {
parent_ref.error().popdown();
});
error.popup();
});
});
}
pub trait ReSetErrorImpl: Send + Sync {
fn error(&self) -> &TemplateChild<error::ReSetError>;
}

View file

@ -0,0 +1,22 @@
use crate::components::base::list_entry_impl;
use adw::glib::Object;
use glib::prelude::IsA;
use gtk::prelude::ListBoxRowExt;
use gtk::Widget;
glib::wrapper! {
pub struct ListEntry(ObjectSubclass<list_entry_impl::ListEntry>)
@extends gtk::ListBoxRow, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Actionable;
}
unsafe impl Send for ListEntry {}
unsafe impl Sync for ListEntry {}
impl ListEntry {
pub fn new(child: &impl IsA<Widget>) -> Self {
let entry: ListEntry = Object::builder().build();
entry.set_child(Some(child));
entry
}
}

View file

@ -0,0 +1,37 @@
use crate::components::base::list_entry;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetListBoxRow.ui")]
pub struct ListEntry {}
#[glib::object_subclass]
impl ObjectSubclass for ListEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetListBoxRow";
type Type = list_entry::ListEntry;
type ParentType = gtk::ListBoxRow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for ListEntry {
fn constructed(&self) {
self.parent_constructed();
}
}
impl ListBoxRowImpl for ListEntry {}
impl WidgetImpl for ListEntry {}
impl WindowImpl for ListEntry {}
impl ApplicationWindowImpl for ListEntry {}

View file

@ -0,0 +1,11 @@
pub mod card_entry;
pub mod card_entry_impl;
pub mod error;
pub mod error_impl;
pub mod list_entry;
pub mod list_entry_impl;
pub mod popup;
pub mod popup_impl;
pub mod setting_box;
pub mod setting_box_impl;
pub mod utils;

View file

@ -0,0 +1,23 @@
use adw::glib::Object;
use gtk::{gdk, Editable, Popover};
use super::popup_impl;
glib::wrapper! {
pub struct Popup(ObjectSubclass<popup_impl::Popup>)
@extends Popover, gtk::Widget,
@implements Editable,gdk::Popup, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl Popup {
pub fn new() -> Self {
let popup: Popup = Object::builder().build();
popup
}
}
impl Default for Popup {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,54 @@
use std::cell::RefCell;
use std::sync::Arc;
use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, Label, PasswordEntry, PasswordEntryBuffer, Popover};
use super::popup;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetPopup.ui")]
pub struct Popup {
#[template_child]
pub reset_popup_label: TemplateChild<Label>,
#[template_child]
pub reset_popup_entry: TemplateChild<PasswordEntry>,
#[template_child]
pub reset_popup_button: TemplateChild<Button>,
pub reset_popup_text: Arc<RefCell<PasswordEntryBuffer>>,
}
unsafe impl Send for Popup {}
unsafe impl Sync for Popup {}
#[glib::object_subclass]
impl ObjectSubclass for Popup {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetPopup";
type Type = popup::Popup;
type ParentType = Popover;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for Popup {
fn constructed(&self) {
self.parent_constructed();
}
}
impl WidgetImpl for Popup {}
impl WindowImpl for Popup {}
impl PopoverImpl for Popup {}
impl ApplicationWindowImpl for Popup {}
impl EditableImpl for Popup {}

View file

@ -0,0 +1,19 @@
use crate::components::base::setting_box_impl;
use adw::glib::Object;
use glib::prelude::IsA;
use gtk::prelude::BoxExt;
use gtk::Widget;
glib::wrapper! {
pub struct SettingBox(ObjectSubclass<setting_box_impl::SettingBox>)
@extends gtk::Box, Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}
impl SettingBox {
pub fn new(child: &impl IsA<Widget>) -> Self {
let entry: SettingBox = Object::builder().build();
entry.append(child);
entry
}
}

View file

@ -0,0 +1,37 @@
use crate::components::base::setting_box;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetSettingBox.ui")]
pub struct SettingBox {}
#[glib::object_subclass]
impl ObjectSubclass for SettingBox {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetSettingBox";
type Type = setting_box::SettingBox;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for SettingBox {
fn constructed(&self) {
self.parent_constructed();
}
}
impl BoxImpl for SettingBox {}
impl WidgetImpl for SettingBox {}
impl WindowImpl for SettingBox {}
impl ApplicationWindowImpl for SettingBox {}

View file

@ -0,0 +1,90 @@
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread,
time::Duration,
};
use dbus::{blocking::Connection, Error};
use gtk::gio;
use crate::components::{
audio::input::source_box::{start_source_box_listener, SourceBox},
audio::output::sink_box::{start_sink_box_listener, SinkBox},
utils::{BASE, DBUS_PATH, WIRELESS},
};
#[derive(Default, PartialEq, Eq)]
pub enum Position {
Connectivity,
Wifi,
Bluetooth,
Audio,
AudioOutput,
AudioInput,
Custom(String),
#[default]
Home,
}
#[derive(Default)]
pub struct Listeners {
pub wifi_disabled: AtomicBool,
pub wifi_listener: AtomicBool,
pub bluetooth_listener: AtomicBool,
pub bluetooth_scan_requested: AtomicBool,
pub pulse_listener: AtomicBool,
}
impl Listeners {
pub fn stop_network_listener(&self) {
if !self.wifi_listener.load(Ordering::SeqCst) {
return;
}
self.wifi_listener.store(false, Ordering::SeqCst);
thread::spawn(|| {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let _: Result<(bool,), Error> = proxy.method_call(WIRELESS, "StopNetworkListener", ());
});
}
pub fn stop_audio_listener(&self) {
self.pulse_listener.store(false, Ordering::SeqCst);
}
pub fn stop_bluetooth_listener(&self) {
self.bluetooth_listener.store(false, Ordering::SeqCst);
}
}
pub fn start_audio_listener(
listeners: Arc<Listeners>,
sink_box: Option<Arc<SinkBox>>,
source_box: Option<Arc<SourceBox>>,
) {
gio::spawn_blocking(move || {
let mut conn = Connection::new_session().unwrap();
if listeners.pulse_listener.load(Ordering::SeqCst) {
return;
}
if let Some(sink_box) = sink_box {
conn = start_sink_box_listener(conn, sink_box);
}
if let Some(source_box) = source_box {
conn = start_source_box_listener(conn, source_box);
}
listeners.pulse_listener.store(true, Ordering::SeqCst);
loop {
let _ = conn.process(Duration::from_millis(1000));
if !listeners.pulse_listener.load(Ordering::SeqCst) {
break;
}
}
});
}

View file

@ -1,44 +0,0 @@
use adw::glib;
use adw::glib::Object;
use adw::subclass::prelude::ObjectSubclassIsExt;
use crate::components::bluetooth::bluetoothBoxImpl;
use crate::components::bluetooth::bluetoothEntry::BluetoothEntry;
use crate::components::bluetooth::bluetoothEntryImpl::DeviceTypes;
glib::wrapper! {
pub struct BluetoothBox(ObjectSubclass<bluetoothBoxImpl::BluetoothBox>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl BluetoothBox {
pub fn new() -> Self {
Object::builder().build()
}
pub fn scanForDevices(&self) {
let selfImp = self.imp();
let mut wifiEntries = selfImp.availableDevices.borrow_mut();
wifiEntries.push(BluetoothEntry::new(DeviceTypes::Mouse, "ina mouse"));
wifiEntries.push(BluetoothEntry::new(DeviceTypes::Keyboard, "inaboard"));
wifiEntries.push(BluetoothEntry::new(DeviceTypes::Controller, "ina controller"));
wifiEntries.push(BluetoothEntry::new(DeviceTypes::Controller, "ina best waifu"));
for wifiEntry in wifiEntries.iter() {
selfImp.resetBluetoothAvailableDevices.append(wifiEntry);
}
}
pub fn addConnectedDevices(&self) {
let selfImp = self.imp();
let mut wifiEntries = selfImp.connectedDevices.borrow_mut();
wifiEntries.push(BluetoothEntry::new(DeviceTypes::Mouse, "why are we still here?"));
wifiEntries.push(BluetoothEntry::new(DeviceTypes::Keyboard, "just to suffer?"));
for wifiEntry in wifiEntries.iter() {
selfImp.resetBluetoothConnectedDevices.append(wifiEntry);
}
}
}

View file

@ -1,54 +0,0 @@
use std::cell::RefCell;
use gtk::{CompositeTemplate, glib, ListBox, Switch};
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use crate::components::bluetooth::bluetoothBox;
use crate::components::bluetooth::bluetoothEntry::BluetoothEntry;
#[allow(non_snake_case)]
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetBluetooth.ui")]
pub struct BluetoothBox {
#[template_child]
pub resetBluetoothSwitch: TemplateChild<Switch>,
#[template_child]
pub resetBluetoothAvailableDevices: TemplateChild<ListBox>,
#[template_child]
pub resetBluetoothConnectedDevices: TemplateChild<ListBox>,
pub availableDevices: RefCell<Vec<BluetoothEntry>>,
pub connectedDevices: RefCell<Vec<BluetoothEntry>>,
}
#[glib::object_subclass]
impl ObjectSubclass for BluetoothBox {
const NAME: &'static str = "resetBluetooth";
type Type = bluetoothBox::BluetoothBox;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
BluetoothEntry::ensure_type();
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for BluetoothBox {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.scanForDevices();
obj.addConnectedDevices();
}
}
impl BoxImpl for BluetoothBox {}
impl WidgetImpl for BluetoothBox {}
impl WindowImpl for BluetoothBox {}
impl ApplicationWindowImpl for BluetoothBox {}

View file

@ -1,31 +0,0 @@
use adw::glib;
use adw::glib::Object;
use adw::subclass::prelude::ObjectSubclassIsExt;
use crate::components::bluetooth::bluetoothEntryImpl;
use crate::components::bluetooth::bluetoothEntryImpl::DeviceTypes;
glib::wrapper! {
pub struct BluetoothEntry(ObjectSubclass<bluetoothEntryImpl::BluetoothEntry>)
@extends gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::Actionable, gtk::ConstraintTarget;
}
impl BluetoothEntry {
pub fn new(deviceType: DeviceTypes, name: &str) -> Self {
let entry: BluetoothEntry = Object::builder().build();
let entryImp = entry.imp();
entryImp.resetBluetoothLabel.get().set_text(name);
entryImp.resetBluetoothDeviceType.get().set_from_icon_name(match deviceType {
DeviceTypes::Mouse => Some("input-mouse-symbolic"),
DeviceTypes::Keyboard => Some("input-keyboard-symbolic"),
DeviceTypes::Headset => Some("audio-headset-symbolic"),
DeviceTypes::Controller => Some("input-gaming-symbolic"),
DeviceTypes::None => Some("text-x-generic-symbolic") // no generic bluetooth device icon found
});
{
let mut wifiName = entryImp.deviceName.borrow_mut();
*wifiName = String::from(name);
}
entry
}
}

View file

@ -0,0 +1,525 @@
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use adw::glib::Object;
use adw::prelude::{ComboRowExt, PreferencesGroupExt};
use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection;
use dbus::message::SignalArgs;
use dbus::{Error, Path};
use glib::prelude::Cast;
use glib::property::PropertySet;
use glib::{clone, ControlFlow};
use gtk::glib::Variant;
use gtk::prelude::{ActionableExt, ButtonExt, ListBoxRowExt, WidgetExt};
use gtk::{gio, StringObject};
use re_set_lib::{
bluetooth::bluetooth_structures::{BluetoothAdapter, BluetoothDevice},
signals::{BluetoothDeviceAdded, BluetoothDeviceChanged, BluetoothDeviceRemoved},
ERROR
};
#[cfg(debug_assertions)]
use re_set_lib::{utils::macros::ErrorLevel, write_log_to_file};
use crate::components::base::error_impl::{show_error, ReSetErrorImpl};
use crate::components::base::utils::Listeners;
use crate::components::bluetooth::bluetooth_box_impl;
use crate::components::bluetooth::bluetooth_entry::BluetoothEntry;
use crate::components::utils::{BASE, BLUETOOTH, DBUS_PATH};
use super::bluetooth_event_handlers::{
device_added_handler, device_changed_handler, device_removed_handler,
};
glib::wrapper! {
pub struct BluetoothBox(ObjectSubclass<bluetooth_box_impl::BluetoothBox>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
unsafe impl Send for BluetoothBox {}
unsafe impl Sync for BluetoothBox {}
impl ReSetErrorImpl for BluetoothBox {
fn error(
&self,
) -> &gtk::subclass::prelude::TemplateChild<crate::components::base::error::ReSetError> {
&self.imp().error
}
}
impl BluetoothBox {
pub fn new(listeners: Arc<Listeners>) -> Arc<Self> {
let obj: Arc<Self> = Arc::new(Object::builder().build());
setup_callbacks(listeners, obj)
}
}
// FUTURE TODO:
// handle bonded -> this means saved but not connected
// handle rssi below x -> don't show device
fn setup_callbacks(
listeners: Arc<Listeners>,
bluetooth_box: Arc<BluetoothBox>,
) -> Arc<BluetoothBox> {
let bluetooth_box_ref = bluetooth_box.clone();
let listeners_ref = listeners.clone();
let imp = bluetooth_box.imp();
imp.reset_switch_initial.set(true);
imp.reset_visibility.set_activatable(true);
imp.reset_visibility
.set_action_name(Some("navigation.push"));
imp.reset_visibility
.set_action_target_value(Some(&Variant::from("visibility")));
imp.reset_bluetooth_main_tab.set_activatable(true);
imp.reset_bluetooth_main_tab
.set_action_name(Some("navigation.pop"));
imp.reset_bluetooth_refresh_button.set_sensitive(false);
imp.reset_bluetooth_refresh_button
.connect_clicked(move |button| {
button.set_sensitive(false);
listeners
.bluetooth_scan_requested
.store(true, Ordering::SeqCst);
});
let bluetooth_box_discover = bluetooth_box.clone();
imp.reset_bluetooth_discoverable_switch
.connect_active_notify(clone!(@weak imp => move |state| {
set_bluetooth_adapter_visibility(
imp.reset_current_bluetooth_adapter.borrow().path.clone(),
state.is_active(),
bluetooth_box_discover.clone()
);
}));
let bluetooth_box_pairable = bluetooth_box.clone();
imp.reset_bluetooth_pairable_switch
.connect_active_notify(clone!(@weak imp => move |state| {
set_bluetooth_adapter_pairability(
imp.reset_current_bluetooth_adapter.borrow().path.clone(),
state.is_active(),
bluetooth_box_pairable.clone()
);
}));
imp.reset_bluetooth_switch
.connect_state_set(move |_, state| {
bluetooth_enabled_switch_handler(
state,
bluetooth_box_ref.clone(),
listeners_ref.clone(),
)
});
bluetooth_box
}
fn bluetooth_enabled_switch_handler(
state: bool,
bluetooth_box_ref: Arc<BluetoothBox>,
listeners_ref: Arc<Listeners>,
) -> glib::Propagation {
let imp = bluetooth_box_ref.imp();
if imp.reset_switch_initial.load(Ordering::SeqCst) {
return glib::Propagation::Proceed;
}
if !state {
let mut available_devices = imp.available_devices.borrow_mut();
let mut current_adapter = imp.reset_current_bluetooth_adapter.borrow_mut();
for entry in available_devices.iter() {
imp.reset_bluetooth_available_devices.remove(&**entry.1);
}
available_devices.clear();
let mut connected_devices = imp.connected_devices.borrow_mut();
for entry in connected_devices.iter() {
imp.reset_bluetooth_connected_devices.remove(&**entry.1);
}
connected_devices.clear();
imp.reset_bluetooth_pairable_switch.set_active(false);
imp.reset_bluetooth_pairable_switch.set_sensitive(false);
imp.reset_bluetooth_discoverable_switch.set_active(false);
imp.reset_bluetooth_discoverable_switch.set_sensitive(false);
imp.reset_bluetooth_refresh_button.set_sensitive(false);
listeners_ref
.bluetooth_listener
.store(false, Ordering::SeqCst);
let res = set_adapter_enabled(
current_adapter.path.clone(),
false,
bluetooth_box_ref.clone(),
);
if res {
current_adapter.powered = false;
}
} else {
let restart_ref = bluetooth_box_ref.clone();
let restart_listener_ref = listeners_ref.clone();
{
let imp = bluetooth_box_ref.imp();
imp.reset_bluetooth_discoverable_switch.set_sensitive(true);
imp.reset_bluetooth_pairable_switch.set_sensitive(true);
}
gio::spawn_blocking(move || {
let mut current_adapter = restart_ref
.imp()
.reset_current_bluetooth_adapter
.borrow_mut();
if set_adapter_enabled(current_adapter.path.clone(), true, restart_ref.clone()) {
current_adapter.powered = true;
start_bluetooth_listener(restart_listener_ref.clone(), restart_ref.clone());
}
});
}
glib::Propagation::Proceed
}
pub fn populate_connected_bluetooth_devices(
listeners: Arc<Listeners>,
bluetooth_box: Arc<BluetoothBox>,
) {
gio::spawn_blocking(move || {
let ref_box = bluetooth_box.clone();
let adapters = get_bluetooth_adapters(ref_box.clone());
let devices = get_bluetooth_devices(ref_box.clone());
{
let imp = bluetooth_box.imp();
let list = imp.reset_model_list.write().unwrap();
let mut model_index = imp.reset_model_index.write().unwrap();
let mut map = imp.reset_bluetooth_adapters.write().unwrap();
if adapters.is_empty() {
return;
}
imp.reset_current_bluetooth_adapter
.replace(adapters.last().unwrap().clone());
for (index, adapter) in adapters.into_iter().enumerate() {
list.append(&adapter.alias);
map.insert(adapter.alias.clone(), (adapter, index as u32));
*model_index += 1;
}
}
start_bluetooth_listener(listeners, ref_box.clone());
glib::spawn_future(async move {
glib::idle_add_once(move || {
let new_adapter_ref = ref_box.clone();
let imp = ref_box.imp();
let list = imp.reset_model_list.read().unwrap();
imp.reset_bluetooth_adapter.set_model(Some(&*list));
let map = imp.reset_bluetooth_adapters.read().unwrap();
let device = imp.reset_current_bluetooth_adapter.borrow();
if let Some(index) = map.get(&device.alias) {
imp.reset_bluetooth_adapter.set_selected(index.1);
}
{
let current_adapter = imp.reset_current_bluetooth_adapter.borrow();
let powered = current_adapter.powered;
imp.reset_bluetooth_switch.set_state(powered);
imp.reset_bluetooth_switch.set_active(powered);
imp.reset_bluetooth_discoverable_switch
.set_active(current_adapter.discoverable);
imp.reset_bluetooth_pairable_switch
.set_active(current_adapter.pairable);
imp.reset_switch_initial.set(false);
}
imp.reset_bluetooth_adapter
.connect_selected_notify(move |dropdown| {
select_bluetooth_adapter_handler(dropdown, new_adapter_ref.clone());
});
for device in devices {
let path = device.path.clone();
let connected = device.connected;
let rssi = device.rssi;
let bluetooth_entry = BluetoothEntry::new(device, ref_box.clone());
if connected {
imp.reset_bluetooth_connected_devices.add(&*bluetooth_entry);
imp.connected_devices
.borrow_mut()
.insert(path, bluetooth_entry.clone());
} else if rssi == -1 {
imp.reset_bluetooth_saved_devices.add(&*bluetooth_entry);
imp.saved_devices
.borrow_mut()
.insert(path, bluetooth_entry.clone());
} else {
imp.reset_bluetooth_available_devices.add(&*bluetooth_entry);
imp.available_devices
.borrow_mut()
.insert(path, bluetooth_entry.clone());
}
}
});
});
});
}
fn select_bluetooth_adapter_handler(
dropdown: &adw::ComboRow,
bluetooth_box: Arc<BluetoothBox>,
) -> ControlFlow {
let imp = bluetooth_box.imp();
let selected = dropdown.selected_item();
if selected.is_none() {
return ControlFlow::Break;
}
let selected = selected.unwrap();
let selected = selected.downcast_ref::<StringObject>().unwrap();
let selected = selected.string().to_string();
let device = imp.reset_bluetooth_adapters.read().unwrap();
let device = device.get(&selected);
if device.is_none() {
return ControlFlow::Break;
}
set_bluetooth_adapter(device.unwrap().0.path.clone(), bluetooth_box.clone());
ControlFlow::Continue
}
pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<BluetoothBox>) {
gio::spawn_blocking(move || {
if listeners.bluetooth_listener.load(Ordering::SeqCst) {
return;
}
let imp = bluetooth_box.imp();
if !imp.reset_current_bluetooth_adapter.borrow().powered {
return;
}
let device_added_box = bluetooth_box.clone();
let device_removed_box = bluetooth_box.clone();
let device_changed_box = bluetooth_box.clone();
let loop_box = bluetooth_box.clone();
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(), Error> = proxy.method_call(BLUETOOTH, "StartBluetoothListener", ());
if res.is_err() {
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to start bluetooth listener");
}
imp.reset_bluetooth_available_devices
.set_description(Some("Scanning..."));
let device_added =
BluetoothDeviceAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let device_removed =
BluetoothDeviceRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let device_changed =
BluetoothDeviceChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let res = conn.add_match(device_added, move |ir: BluetoothDeviceAdded, _, _| {
device_added_handler(device_added_box.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on bluetooth device add event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(device_removed, move |ir: BluetoothDeviceRemoved, _, _| {
device_removed_handler(device_removed_box.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on bluetooth device remove event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(device_changed, move |ir: BluetoothDeviceChanged, _, _| {
device_changed_handler(device_changed_box.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on bluetooth device remove event",
ErrorLevel::PartialBreakage
);
return;
}
listeners.bluetooth_listener.store(true, Ordering::SeqCst);
let time = SystemTime::now();
let listener_active = true;
bluetooth_listener_loop(
&conn,
listeners,
proxy,
bluetooth_box,
loop_box,
listener_active,
time,
);
});
}
fn bluetooth_listener_loop(
conn: &Connection,
listeners: Arc<Listeners>,
proxy: dbus::blocking::Proxy<'_, &Connection>,
bluetooth_box: Arc<BluetoothBox>,
loop_box: Arc<BluetoothBox>,
mut listener_active: bool,
mut time: SystemTime,
) {
loop {
let _ = conn.process(Duration::from_millis(1000));
if !listeners.bluetooth_listener.load(Ordering::SeqCst) {
let res: Result<(), Error> = proxy.method_call(BLUETOOTH, "StopBluetoothListener", ());
if res.is_err() {
show_error::<BluetoothBox>(
bluetooth_box.clone(),
"Failed to stop bluetooth listener",
);
}
loop_box
.imp()
.reset_bluetooth_available_devices
.set_description(None);
break;
}
if listener_active && time.elapsed().unwrap() > Duration::from_millis(10000) {
listener_active = false;
let instance_ref = loop_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
instance_ref
.imp()
.reset_bluetooth_refresh_button
.set_sensitive(true);
});
});
let res: Result<(), Error> = proxy.method_call(BLUETOOTH, "StopBluetoothScan", ());
if res.is_err() {
show_error::<BluetoothBox>(
bluetooth_box.clone(),
"Failed to stop bluetooth listener",
);
}
loop_box
.imp()
.reset_bluetooth_available_devices
.set_description(None);
}
if !listener_active && listeners.bluetooth_scan_requested.load(Ordering::SeqCst) {
listeners
.bluetooth_scan_requested
.store(false, Ordering::SeqCst);
listener_active = true;
let res: Result<(), Error> = proxy.method_call(BLUETOOTH, "StartBluetoothListener", ());
if res.is_err() {
show_error::<BluetoothBox>(
bluetooth_box.clone(),
"Failed to start bluetooth listener",
);
}
loop_box
.imp()
.reset_bluetooth_available_devices
.set_description(Some("Scanning..."));
time = SystemTime::now();
}
}
}
fn get_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) -> Vec<BluetoothDevice> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<BluetoothDevice>,), Error> =
proxy.method_call(BLUETOOTH, "GetBluetoothDevices", ());
if res.is_err() {
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to get bluetooth devices");
return Vec::new();
}
res.unwrap().0
}
fn get_bluetooth_adapters(bluetooth_box: Arc<BluetoothBox>) -> Vec<BluetoothAdapter> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<BluetoothAdapter>,), Error> =
proxy.method_call(BLUETOOTH, "GetBluetoothAdapters", ());
if res.is_err() {
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to get bluetooth adapters");
return Vec::new();
}
res.unwrap().0
}
fn set_bluetooth_adapter(path: Path<'static>, bluetooth_box: Arc<BluetoothBox>) {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Path<'static>,), Error> =
proxy.method_call(BLUETOOTH, "SetBluetoothAdapter", (path,));
if res.is_err() {
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to set bluetooth adapter");
}
}
fn set_bluetooth_adapter_visibility(
path: Path<'static>,
visible: bool,
bluetooth_box: Arc<BluetoothBox>,
) {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(bool,), Error> = proxy.method_call(
BLUETOOTH,
"SetBluetoothAdapterDiscoverability",
(path, visible),
);
if res.is_err() {
show_error::<BluetoothBox>(
bluetooth_box.clone(),
"Failed to set bluetooth adapter visibility",
);
}
}
fn set_bluetooth_adapter_pairability(
path: Path<'static>,
visible: bool,
bluetooth_box: Arc<BluetoothBox>,
) {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(bool,), Error> =
proxy.method_call(BLUETOOTH, "SetBluetoothAdapterPairability", (path, visible));
if res.is_err() {
show_error::<BluetoothBox>(
bluetooth_box.clone(),
"Failed to set bluetooth adapter pairability",
);
}
}
fn set_adapter_enabled(
path: Path<'static>,
enabled: bool,
bluetooth_box: Arc<BluetoothBox>,
) -> bool {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let result: Result<(bool,), Error> =
proxy.method_call(BLUETOOTH, "SetBluetoothAdapterEnabled", (path, enabled));
if result.is_err() {
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to enable bluetooth adapter");
return false;
}
result.unwrap().0
}

View file

@ -0,0 +1,84 @@
use adw::{ActionRow, ComboRow, PreferencesGroup, SwitchRow};
use dbus::Path;
use gtk::subclass::prelude::*;
use gtk::{prelude::*, StringList};
use gtk::{Button, CompositeTemplate, Switch};
use re_set_lib::bluetooth::bluetooth_structures::BluetoothAdapter;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock};
use crate::components::base::error::ReSetError;
use crate::components::base::list_entry::ListEntry;
use crate::components::bluetooth::bluetooth_box;
use crate::components::bluetooth::bluetooth_entry::BluetoothEntry;
type BluetoothMap = RefCell<HashMap<Path<'static>, Arc<BluetoothEntry>>>;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetBluetooth.ui")]
pub struct BluetoothBox {
#[template_child]
pub reset_bluetooth_switch: TemplateChild<Switch>,
#[template_child]
pub reset_bluetooth_available_devices: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_bluetooth_saved_devices: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_bluetooth_refresh_button: TemplateChild<Button>,
#[template_child]
pub reset_bluetooth_adapter: TemplateChild<ComboRow>,
#[template_child]
pub reset_bluetooth_connected_devices: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_visibility: TemplateChild<ActionRow>,
#[template_child]
pub reset_bluetooth_main_tab: TemplateChild<ActionRow>,
#[template_child]
pub reset_bluetooth_discoverable_switch: TemplateChild<SwitchRow>,
#[template_child]
pub reset_bluetooth_pairable_switch: TemplateChild<SwitchRow>,
#[template_child]
pub error: TemplateChild<ReSetError>,
pub available_devices: BluetoothMap,
pub connected_devices: BluetoothMap,
pub saved_devices: BluetoothMap,
pub reset_bluetooth_adapters: Arc<RwLock<HashMap<String, (BluetoothAdapter, u32)>>>,
pub reset_current_bluetooth_adapter: Arc<RefCell<BluetoothAdapter>>,
pub reset_model_list: Arc<RwLock<StringList>>,
pub reset_model_index: Arc<RwLock<u32>>,
pub reset_switch_initial: AtomicBool,
}
#[glib::object_subclass]
impl ObjectSubclass for BluetoothBox {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetBluetooth";
type Type = bluetooth_box::BluetoothBox;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
BluetoothEntry::ensure_type();
ListEntry::ensure_type();
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for BluetoothBox {
fn constructed(&self) {
self.parent_constructed();
}
}
impl BoxImpl for BluetoothBox {}
impl WidgetImpl for BluetoothBox {}
impl WindowImpl for BluetoothBox {}
impl ApplicationWindowImpl for BluetoothBox {}

View file

@ -0,0 +1,166 @@
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use crate::components::base::error_impl::show_error;
use crate::components::bluetooth::bluetooth_entry_impl;
use crate::components::utils::{BASE, BLUETOOTH, DBUS_PATH};
use adw::glib::Object;
use adw::prelude::{ActionRowExt, PreferencesRowExt};
use adw::ActionRow;
use dbus::blocking::Connection;
use dbus::{Error, Path};
use glib::subclass::prelude::ObjectSubclassIsExt;
use gtk::prelude::{ButtonExt, ListBoxRowExt, WidgetExt};
use gtk::{gio, Align, Button, GestureClick, Image, Label};
use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice;
use super::bluetooth_box::BluetoothBox;
glib::wrapper! {
pub struct BluetoothEntry(ObjectSubclass<bluetooth_entry_impl::BluetoothEntry>)
@extends ActionRow, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::Actionable, gtk::ConstraintTarget, gtk::ListBoxRow, adw::PreferencesRow;
}
unsafe impl Send for BluetoothEntry {}
unsafe impl Sync for BluetoothEntry {}
impl BluetoothEntry {
pub fn new(device: BluetoothDevice, bluetooth_box: Arc<BluetoothBox>) -> Arc<Self> {
let entry: Arc<BluetoothEntry> = Arc::new(Object::builder().build());
let entry_imp = entry.imp();
let entry_ref = entry.clone();
let entry_ref_remove = entry.clone();
entry.set_title(&device.alias);
entry.set_subtitle(&device.address);
entry.set_activatable(true);
entry_imp.remove_device_button.replace(
Button::builder()
.icon_name("user-trash-symbolic")
.valign(Align::Center)
.build(),
);
entry_imp
.connecting_label
.replace(Label::builder().label("").build());
entry.add_suffix(entry_imp.remove_device_button.borrow().deref());
if device.icon.is_empty() {
entry.add_prefix(&Image::from_icon_name("dialog-question-symbolic"));
} else {
entry.add_prefix(&Image::from_icon_name(&device.icon));
}
if device.connected || device.bonded {
entry_imp.remove_device_button.borrow().set_sensitive(true);
} else {
entry_imp.remove_device_button.borrow().set_sensitive(false);
}
entry_imp.bluetooth_device.replace(device);
entry_imp
.remove_device_button
.borrow()
.connect_clicked(move |_| {
let imp = entry_ref_remove.imp();
remove_device_pairing(
imp.bluetooth_device.borrow().path.clone(),
bluetooth_box.clone(),
);
});
let gesture = GestureClick::new();
// paired is not what we think
// FUTURE TODO: implement paired
gesture.connect_released(move |_, _, _, _| {
let imp = entry_ref.imp();
let borrow = imp.bluetooth_device.borrow();
if borrow.connected {
let imp = entry_ref.imp();
imp.remove_device_button.borrow().set_sensitive(false);
imp.connecting_label.borrow().set_text("Disconnecting...");
disconnect_from_device(entry_ref.clone(), borrow.path.clone());
} else {
entry_ref.set_sensitive(false);
imp.connecting_label.borrow().set_text("Connecting...");
connect_to_device(entry_ref.clone(), borrow.path.clone());
}
});
entry.add_controller(gesture);
entry
}
}
fn connect_to_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(bool,), Error> =
proxy.method_call(BLUETOOTH, "ConnectToBluetoothDevice", (path,));
glib::spawn_future(async move {
glib::idle_add_once(move || {
if res.is_err() {
entry.set_sensitive(true);
entry
.imp()
.connecting_label
.borrow()
.set_text("Error on connecting");
} else {
entry.set_sensitive(true);
entry.imp().connecting_label.borrow().set_text("");
}
});
});
});
}
// fn pair_with_device(path: Path<'static>) {
// gio::spawn_blocking(move || {
// let conn = Connection::new_session().unwrap();
// let proxy = conn.with_proxy(
// BASE,
// DBUS_PATH,
// Duration::from_millis(1000),
// );
// let _: Result<(bool,), Error> = proxy.method_call(
// BLUETOOTH,
// "PairWithBluetoothDevice",
// (path,),
// );
// });
// }
fn disconnect_from_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(bool,), Error> =
proxy.method_call(BLUETOOTH, "DisconnectFromBluetoothDevice", (path,));
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = entry.imp();
if res.is_err() {
imp.remove_device_button.borrow().set_sensitive(true);
imp.connecting_label
.borrow()
.set_text("Error on disconnecting");
} else {
imp.remove_device_button.borrow().set_sensitive(true);
imp.connecting_label.borrow().set_text("");
}
});
});
});
}
fn remove_device_pairing(path: Path<'static>, wifi_box: Arc<BluetoothBox>) {
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(bool,), Error> =
proxy.method_call(BLUETOOTH, "RemoveDevicePairing", (path,));
if res.is_err() {
show_error::<BluetoothBox>(wifi_box.clone(), "Failed to remove device pairing");
}
});
}

View file

@ -1,35 +1,27 @@
use std::cell::RefCell;
use gtk::{Button, CompositeTemplate, glib, Image, Label};
use crate::components::bluetooth::bluetooth_entry;
use adw::subclass::action_row::ActionRowImpl;
use adw::subclass::preferences_row::PreferencesRowImpl;
use adw::ActionRow;
use gtk::subclass::prelude::*;
use crate::components::bluetooth::bluetoothEntry;
use gtk::{Button, CompositeTemplate, Label};
use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice;
use std::cell::RefCell;
#[derive(Default, Copy, Clone)]
pub enum DeviceTypes {
Mouse,
Keyboard,
Headset,
Controller,
#[default]
None,
}
#[allow(non_snake_case)]
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetBluetoothEntry.ui")]
pub struct BluetoothEntry {
#[template_child]
pub resetBluetoothDeviceType: TemplateChild<Image>,
#[template_child]
pub resetBluetoothLabel: TemplateChild<Label>,
#[template_child]
pub resetBluetoothButton: TemplateChild<Button>,
pub deviceName: RefCell<String>,
pub remove_device_button: RefCell<Button>,
pub connecting_label: RefCell<Label>,
pub device_name: RefCell<String>,
pub bluetooth_device: RefCell<BluetoothDevice>,
}
#[glib::object_subclass]
impl ObjectSubclass for BluetoothEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetBluetoothEntry";
type Type = bluetoothEntry::BluetoothEntry;
type ParentType = gtk::ListBoxRow;
type Type = bluetooth_entry::BluetoothEntry;
type ParentType = ActionRow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
@ -46,6 +38,10 @@ impl ObjectImpl for BluetoothEntry {
}
}
impl ActionRowImpl for BluetoothEntry {}
impl PreferencesRowImpl for BluetoothEntry {}
impl ListBoxRowImpl for BluetoothEntry {}
impl WidgetImpl for BluetoothEntry {}

View file

@ -0,0 +1,104 @@
use std::sync::Arc;
use adw::prelude::PreferencesGroupExt;
use glib::subclass::types::ObjectSubclassIsExt;
use gtk::prelude::WidgetExt;
use re_set_lib::signals::{BluetoothDeviceAdded, BluetoothDeviceChanged, BluetoothDeviceRemoved};
use super::{bluetooth_box::BluetoothBox, bluetooth_entry::BluetoothEntry};
pub fn device_changed_handler(
device_changed_box: Arc<BluetoothBox>,
ir: BluetoothDeviceChanged,
) -> bool {
let bluetooth_box = device_changed_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = bluetooth_box.imp();
let rssi = ir.bluetooth_device.rssi;
let mut map = imp.available_devices.borrow_mut();
if let Some(list_entry) = map.get_mut(&ir.bluetooth_device.path) {
let mut existing_bluetooth_device = list_entry.imp().bluetooth_device.borrow_mut();
if existing_bluetooth_device.connected != ir.bluetooth_device.connected {
if ir.bluetooth_device.connected {
imp.reset_bluetooth_available_devices.remove(&**list_entry);
imp.reset_bluetooth_saved_devices.remove(&**list_entry);
imp.reset_bluetooth_connected_devices.add(&**list_entry);
} else if rssi == -1 {
imp.reset_bluetooth_connected_devices.remove(&**list_entry);
imp.reset_bluetooth_saved_devices.add(&**list_entry);
imp.reset_bluetooth_available_devices.remove(&**list_entry);
} else {
imp.reset_bluetooth_connected_devices.remove(&**list_entry);
imp.reset_bluetooth_saved_devices.remove(&**list_entry);
imp.reset_bluetooth_available_devices.add(&**list_entry);
}
}
if existing_bluetooth_device.bonded != ir.bluetooth_device.bonded {
if ir.bluetooth_device.bonded {
list_entry
.imp()
.remove_device_button
.borrow()
.set_sensitive(true);
} else {
list_entry
.imp()
.remove_device_button
.borrow()
.set_sensitive(false);
}
}
*existing_bluetooth_device = ir.bluetooth_device;
}
});
});
true
}
pub fn device_removed_handler(
device_removed_box: Arc<BluetoothBox>,
ir: BluetoothDeviceRemoved,
) -> bool {
let bluetooth_box = device_removed_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = bluetooth_box.imp();
let mut map = imp.available_devices.borrow_mut();
if let Some(list_entry) = map.remove(&ir.bluetooth_device) {
if list_entry.imp().bluetooth_device.borrow().connected {
imp.reset_bluetooth_connected_devices.remove(&*list_entry);
} else {
// FUTURE TODO: is there a better way for this?
imp.reset_bluetooth_available_devices.remove(&*list_entry);
imp.reset_bluetooth_saved_devices.remove(&*list_entry);
}
}
});
});
true
}
pub fn device_added_handler(device_added_box: Arc<BluetoothBox>, ir: BluetoothDeviceAdded) -> bool {
let bluetooth_box = device_added_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = bluetooth_box.imp();
let path = ir.bluetooth_device.path.clone();
let rssi = ir.bluetooth_device.rssi;
let connected = ir.bluetooth_device.connected;
let bluetooth_entry = BluetoothEntry::new(ir.bluetooth_device, bluetooth_box.clone());
imp.available_devices
.borrow_mut()
.insert(path, bluetooth_entry.clone());
if connected {
imp.reset_bluetooth_connected_devices.add(&*bluetooth_entry);
} else if rssi == -1 {
imp.reset_bluetooth_saved_devices.add(&*bluetooth_entry);
} else {
imp.reset_bluetooth_available_devices.add(&*bluetooth_entry);
}
});
});
true
}

View file

@ -1,5 +1,5 @@
#![allow(non_snake_case)]
pub mod bluetoothBox;
pub mod bluetoothEntry;
pub mod bluetoothBoxImpl;
pub mod bluetoothEntryImpl;
pub mod bluetooth_box;
pub mod bluetooth_box_impl;
pub mod bluetooth_entry;
pub mod bluetooth_entry_impl;
mod bluetooth_event_handlers;

View file

@ -1,4 +1,7 @@
pub mod window;
pub mod wifi;
pub mod audio;
pub mod base;
pub mod bluetooth;
pub mod audio;
mod plugin;
pub mod utils;
pub mod wifi;
pub mod window;

View file

@ -0,0 +1,95 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use gtk::FlowBox;
use crate::components::{base::utils::{Listeners, Position}, utils::Capabilities};
// extern "C" {
// pub fn startup() -> SidebarInfo;
// pub fn shutdown();
// pub fn run_test();
// }
pub type RegularClickEvent = fn(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefCell<Position>>);
pub type PluginClickEvent = Rc<dyn Fn(FlowBox, Rc<RefCell<Position>>, Vec<gtk::Box>)>;
pub trait TSideBarInfo {
fn name(&self) -> &'static str;
fn icon_name(&self) -> &'static str;
fn parent(&self) -> Option<&'static str>;
fn regular_click_event(&self) -> Option<RegularClickEvent>;
fn plugin_click_event(&self) -> PluginClickEvent;
fn plugin_boxes(&self) -> Option<Vec<gtk::Box>>;
}
pub struct ReSetSidebarInfo {
pub name: &'static str,
pub icon_name: &'static str,
pub parent: Option<&'static str>,
// pub pre_click:
pub click_event: RegularClickEvent,
// pub post_click:
}
impl TSideBarInfo for ReSetSidebarInfo {
fn name(&self) -> &'static str {
self.name
}
fn icon_name(&self) -> &'static str {
self.icon_name
}
fn parent(&self) -> Option<&'static str> {
self.parent
}
fn regular_click_event(&self) -> Option<RegularClickEvent> {
Some(self.click_event)
}
fn plugin_click_event(&self) -> PluginClickEvent {
Rc::new(|_, _, _| {})
}
fn plugin_boxes(&self) -> Option<Vec<gtk::Box>> {
None
}
}
#[repr(C)]
pub struct PluginSidebarInfo {
pub name: &'static str,
pub icon_name: &'static str,
pub parent: Option<&'static str>,
pub click_event: PluginClickEvent,
pub plugin_boxes: Vec<gtk::Box>,
}
impl TSideBarInfo for PluginSidebarInfo {
fn name(&self) -> &'static str {
self.name
}
fn icon_name(&self) -> &'static str {
self.icon_name
}
fn parent(&self) -> Option<&'static str> {
self.parent
}
fn regular_click_event(&self) -> Option<RegularClickEvent> {
None
}
fn plugin_click_event(&self) -> PluginClickEvent {
self.click_event.clone()
}
fn plugin_boxes(&self) -> Option<Vec<gtk::Box>> {
Some(self.plugin_boxes.clone())
}
}

View file

@ -0,0 +1 @@
pub mod function;

107
src/components/utils.rs Normal file
View file

@ -0,0 +1,107 @@
use std::cell::Cell;
use std::time::Duration;
use adw::gdk::pango::EllipsizeMode;
use adw::prelude::ListModelExtManual;
use adw::{ActionRow, ComboRow};
use dbus::blocking::Connection;
use dbus::Error;
use glib::prelude::Cast;
use glib::Object;
use gtk::prelude::{GObjectPropertyExpressionExt, ListBoxRowExt, ListItemExt, WidgetExt};
use gtk::{Align, SignalListItemFactory, StringObject};
use re_set_lib::ERROR;
#[cfg(debug_assertions)]
use re_set_lib::{utils::macros::ErrorLevel, write_log_to_file};
pub const DBUS_PATH: &str = "/org/Xetibo/ReSet/Daemon";
pub const WIRELESS: &str = "org.Xetibo.ReSet.Network";
pub const BLUETOOTH: &str = "org.Xetibo.ReSet.Bluetooth";
pub const AUDIO: &str = "org.Xetibo.ReSet.Audio";
pub const BASE: &str = "org.Xetibo.ReSet.Daemon";
#[derive(Default)]
pub struct Capabilities {
pub wifi: Cell<bool>,
pub bluetooth: Cell<bool>,
pub audio: Cell<bool>,
}
impl Capabilities {
pub fn set(&self, wifi: bool, bluetooth: bool, audio: bool) {
self.wifi.set(wifi);
self.bluetooth.set(bluetooth);
self.audio.set(audio);
}
}
pub fn create_dropdown_label_factory() -> SignalListItemFactory {
let factory = SignalListItemFactory::new();
factory.connect_setup(|_, item| {
let item = item.downcast_ref::<gtk::ListItem>().unwrap();
let label = gtk::Label::new(None);
label.set_halign(Align::Start);
item.property_expression("item")
.chain_property::<StringObject>("string")
.bind(&label, "label", gtk::Widget::NONE);
item.set_child(Some(&label));
});
factory
}
pub fn set_combo_row_ellipsis(element: ComboRow) {
for (i, child) in element
.child()
.unwrap()
.observe_children()
.iter::<Object>()
.enumerate()
{
if i == 2 {
if let Ok(object) = child {
if let Some(item) = object.downcast_ref::<gtk::Box>() {
if let Some(widget) = item.first_child() {
if let Some(label) = widget.downcast_ref::<gtk::Label>() {
label.set_ellipsize(EllipsizeMode::End);
label.set_max_width_chars(1);
}
}
}
}
}
}
}
pub fn set_action_row_ellipsis(element: ActionRow) {
let option = element.first_child();
if let Some(first_box) = option {
for (i, child) in first_box.observe_children().iter::<Object>().enumerate() {
if i == 2 {
if let Ok(object) = child {
if let Some(item) = object.downcast_ref::<gtk::Box>() {
if let Some(widget) = item.first_child() {
if let Some(label) = widget.downcast_ref::<gtk::Label>() {
label.set_ellipsize(EllipsizeMode::End);
label.set_max_width_chars(1);
}
}
}
}
}
}
}
}
pub fn get_capabilities() -> Vec<String> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(10000));
let res: Result<(Vec<String>,), Error> = proxy.method_call(BASE, "GetCapabilities", ());
if res.is_err() {
ERROR!(
"Could not call capabilities from daemon",
ErrorLevel::Critical
);
return Vec::new();
}
res.unwrap().0
}

View file

@ -1,5 +1,14 @@
#![allow(non_snake_case)]
pub mod wifiBox;
pub mod wifiBoxImpl;
pub mod wifiEntry;
pub mod wifiEntryImpl;
pub mod saved_wifi_entry;
pub mod saved_wifi_entry_impl;
pub mod utils;
pub mod wifi_address_entry;
pub mod wifi_address_entry_impl;
pub mod wifi_box;
pub mod wifi_box_impl;
pub mod wifi_entry;
pub mod wifi_entry_impl;
mod wifi_event_handlers;
pub mod wifi_options;
pub mod wifi_options_impl;
pub mod wifi_route_entry;
pub mod wifi_route_entry_impl;

View file

@ -0,0 +1,72 @@
use std::rc::Rc;
use std::time::Duration;
use crate::components::utils::{BASE, DBUS_PATH, WIRELESS};
use crate::components::wifi::saved_wifi_entry_impl;
use crate::components::wifi::utils::get_connection_settings;
use crate::components::wifi::wifi_box_impl::WifiBox;
use crate::components::wifi::wifi_options::WifiOptions;
use adw::glib::Object;
use adw::prelude::{ActionRowExt, ButtonExt, PreferencesGroupExt, PreferencesRowExt};
use dbus::blocking::Connection;
use dbus::{Error, Path};
use glib::clone;
use glib::property::PropertySet;
use glib::subclass::types::ObjectSubclassIsExt;
use gtk::prelude::{BoxExt, ListBoxRowExt};
use gtk::{gio, Align, Button, Orientation};
glib::wrapper! {
pub struct SavedWifiEntry(ObjectSubclass<saved_wifi_entry_impl::SavedWifiEntry>)
@extends adw::ActionRow, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::Actionable, gtk::ConstraintTarget, adw::PreferencesRow, gtk::ListBoxRow;
}
impl SavedWifiEntry {
pub fn new(name: &str, path: Path<'static>, wifi_box: &WifiBox) -> Rc<Self> {
let entry: Rc<SavedWifiEntry> = Rc::new(Object::builder().build());
entry.set_activatable(false);
let entry_imp = entry.imp();
entry.set_title(name);
entry_imp.reset_connection_path.set(path);
let edit_button = Button::builder()
.icon_name("document-edit-symbolic")
.valign(Align::Center)
.build();
let delete_button = Button::builder()
.icon_name("user-trash-symbolic")
.valign(Align::Center)
.build();
let suffix_box = gtk::Box::new(Orientation::Horizontal, 5);
suffix_box.append(&edit_button);
suffix_box.append(&delete_button);
entry.add_suffix(&suffix_box);
edit_button.connect_clicked(
clone!(@ weak entry_imp, @ weak wifi_box => move |_| {
let _option = get_connection_settings(entry_imp.reset_connection_path.borrow().clone());
wifi_box.reset_wifi_navigation.push(&*WifiOptions::new(_option, entry_imp.reset_connection_path.borrow().clone()));
}),
);
let entry_ref = entry.clone();
delete_button.connect_clicked(clone!(@weak wifi_box => move |_| {
delete_connection(entry_ref.imp().reset_connection_path.take());
// FUTURE TODO: handle error
wifi_box.reset_stored_wifi_list.remove(&*entry_ref);
}));
entry
}
}
fn delete_connection(path: Path<'static>) {
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let _: Result<(), Error> = proxy.method_call(WIRELESS, "DeleteConnection", (path,));
});
}

View file

@ -0,0 +1,55 @@
use adw::subclass::preferences_row::PreferencesRowImpl;
use adw::subclass::prelude::ActionRowImpl;
use adw::ActionRow;
use re_set_lib::network::network_structures::AccessPoint;
use std::cell::RefCell;
use dbus::Path;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
use super::saved_wifi_entry;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetSavedWifiEntry.ui")]
pub struct SavedWifiEntry {
pub reset_connection_path: RefCell<Path<'static>>,
pub access_point: RefCell<AccessPoint>,
}
unsafe impl Send for SavedWifiEntry {}
unsafe impl Sync for SavedWifiEntry {}
#[glib::object_subclass]
impl ObjectSubclass for SavedWifiEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetSavedWifiEntry";
type Type = saved_wifi_entry::SavedWifiEntry;
type ParentType = ActionRow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for SavedWifiEntry {
fn constructed(&self) {
self.parent_constructed();
}
}
impl PreferencesRowImpl for SavedWifiEntry {}
impl ListBoxRowImpl for SavedWifiEntry {}
impl ActionRowImpl for SavedWifiEntry {}
impl WidgetImpl for SavedWifiEntry {}
impl WindowImpl for SavedWifiEntry {}
impl ApplicationWindowImpl for SavedWifiEntry {}

View file

@ -0,0 +1,36 @@
use dbus::arg::RefArg;
use dbus::blocking::Connection;
use dbus::Error;
use dbus::Path;
use re_set_lib::network::connection::Connection as ResetConnection;
use std::collections::HashMap;
use std::time::Duration;
use crate::components::utils::BASE;
use crate::components::utils::DBUS_PATH;
use crate::components::utils::WIRELESS;
#[derive(Default, Copy, Clone)]
pub enum IpProtocol {
#[default]
IPv4,
IPv6,
}
type ResultType =
Result<(HashMap<String, HashMap<String, dbus::arg::Variant<Box<dyn RefArg>>>>,), Error>;
pub fn get_connection_settings(path: Path<'static>) -> ResetConnection {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: ResultType = proxy.method_call(WIRELESS, "GetConnectionSettings", (path,));
if res.is_err() {
ResetConnection::default();
}
let (res,) = res.unwrap();
let res = ResetConnection::convert_from_propmap(res);
if res.is_err() {
ResetConnection::default();
}
res.unwrap()
}

View file

@ -1,59 +0,0 @@
use std::thread;
use std::time::Duration;
use adw::glib;
use adw::glib::clone;
use adw::glib::Object;
use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection;
use dbus::Error;
use gtk::prelude::ButtonExt;
use crate::components::wifi::wifiBoxImpl;
use crate::components::wifi::wifiEntry::WifiEntry;
use crate::components::wifi::wifiEntryImpl::WifiStrength;
glib::wrapper! {
pub struct WifiBox(ObjectSubclass<wifiBoxImpl::WifiBox>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl WifiBox {
pub fn new() -> Self {
Object::builder().build()
}
pub fn setupCallbacks(&self) {
let selfImp = self.imp();
selfImp.resetWifiDetails.connect_row_activated(clone!(@ weak selfImp as window => move |_, _y| {
// let result = y.downcast_ref()::<WifiEntry>().unwrap(); no worky smh
}));
}
pub fn scanForWifi(&self) {
let selfImp = self.imp();
let mut wifiEntries = selfImp.wifiEntries.borrow_mut();
wifiEntries.push(WifiEntry::new(WifiStrength::Excellent, "ina internet", true));
wifiEntries.push(WifiEntry::new(WifiStrength::Excellent, "watch ina", true));
wifiEntries.push(WifiEntry::new(WifiStrength::Ok, "INANET", true));
wifiEntries.push(WifiEntry::new(WifiStrength::Weak, "ina best waifu", false));
for wifiEntry in wifiEntries.iter() {
selfImp.resetWifiList.append(wifiEntry);
}
}
pub fn donotdisturb() {
thread::spawn(|| {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
Duration::from_millis(1000),
);
let _: Result<(), Error> = proxy.method_call("org.freedesktop.Notifications", "DoNotDisturb", ());
});
}
}

View file

@ -1,58 +0,0 @@
use std::cell::RefCell;
use gtk::{Button, CompositeTemplate, glib, ListBox, ListBoxRow, Revealer, Switch};
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use crate::components::wifi::wifiBox;
use crate::components::wifi::wifiEntry::WifiEntry;
#[allow(non_snake_case)]
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetWiFi.ui")]
pub struct WifiBox {
#[template_child]
pub resetWifiDetails: TemplateChild<ListBox>,
#[template_child]
pub resetWifiSwitchRow: TemplateChild<ListBoxRow>,
#[template_child]
pub resetWifiSwitch: TemplateChild<Switch>,
#[template_child]
pub resetWifiList: TemplateChild<ListBox>,
#[template_child]
pub resetWifiAdvanced: TemplateChild<Button>,
pub wifiEntries: RefCell<Vec<WifiEntry>>,
}
#[glib::object_subclass]
impl ObjectSubclass for WifiBox {
const NAME: &'static str = "resetWifi";
type Type = wifiBox::WifiBox;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
WifiEntry::ensure_type();
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for WifiBox {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.setupCallbacks();
obj.scanForWifi();
}
}
impl BoxImpl for WifiBox {}
impl WidgetImpl for WifiBox {}
impl WindowImpl for WifiBox {}
impl ApplicationWindowImpl for WifiBox {}

View file

@ -1,33 +0,0 @@
use crate::components::wifi::wifiEntryImpl;
use adw::glib;
use adw::glib::{Object, PropertySet};
use adw::subclass::prelude::ObjectSubclassIsExt;
use gtk::prelude::WidgetExt;
use crate::components::wifi::wifiEntryImpl::WifiStrength;
glib::wrapper! {
pub struct WifiEntry(ObjectSubclass<wifiEntryImpl::WifiEntry>)
@extends gtk::ListBoxRow, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::Actionable, gtk::ConstraintTarget;
}
impl WifiEntry {
pub fn new(strength: WifiStrength, name: &str, isEncrypted: bool) -> Self {
let entry: WifiEntry = Object::builder().build();
let entryImp = entry.imp();
entryImp.wifiStrength.set(strength);
entryImp.resetWifiLabel.get().set_text(name);
entryImp.resetWifiEncrypted.set_visible(isEncrypted);
entryImp.resetWifiStrength.get().set_from_icon_name(match strength {
WifiStrength::Excellent => Some("network-wireless-signal-excellent-symbolic"),
WifiStrength::Ok => Some("network-wireless-signal-ok-symbolic"),
WifiStrength::Weak => Some("network-wireless-signal-weak-symbolic"),
WifiStrength::None => Some("network-wireless-signal-none-symbolic"),
});
{
let mut wifiName = entryImp.wifiName.borrow_mut();
*wifiName = String::from(name);
}
entry
}
}

View file

@ -1,58 +0,0 @@
use std::cell::RefCell;
use gtk::{Button, CompositeTemplate, glib, Image, Label};
use gtk::subclass::prelude::*;
use crate::components::wifi::wifiEntry;
#[derive(Default, Copy, Clone)]
pub enum WifiStrength {
Excellent,
Ok,
Weak,
#[default]
None,
}
#[allow(non_snake_case)]
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetWifiEntry.ui")]
pub struct WifiEntry {
#[template_child]
pub resetWifiStrength: TemplateChild<Image>,
#[template_child]
pub resetWifiEncrypted: TemplateChild<Image>,
#[template_child]
pub resetWifiLabel: TemplateChild<Label>,
#[template_child]
pub resetWifiButton: TemplateChild<Button>,
pub wifiName: RefCell<String>,
pub wifiStrength: RefCell<WifiStrength>,
}
#[glib::object_subclass]
impl ObjectSubclass for WifiEntry {
const NAME: &'static str = "resetWifiEntry";
type Type = wifiEntry::WifiEntry;
type ParentType = gtk::ListBoxRow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for WifiEntry {
fn constructed(&self) {
self.parent_constructed();
}
}
impl ListBoxRowImpl for WifiEntry {}
impl WidgetImpl for WifiEntry {}
impl WindowImpl for WifiEntry {}
impl ApplicationWindowImpl for WifiEntry {}

View file

@ -0,0 +1,160 @@
use re_set_lib::network::connection::Address;
use std::cell::RefCell;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::rc::Rc;
use std::str::FromStr;
use adw::glib::Object;
use adw::prelude::PreferencesRowExt;
use glib::clone;
use glib::subclass::prelude::ObjectSubclassIsExt;
use gtk::prelude::{ButtonExt, EditableExt, WidgetExt};
use re_set_lib::network::connection::Connection;
use crate::components::wifi::utils::IpProtocol;
use crate::components::wifi::wifi_address_entry_impl;
use crate::components::wifi::wifi_address_entry_impl::WifiAddressEntryImpl;
glib::wrapper! {
pub struct WifiAddressEntry(ObjectSubclass<wifi_address_entry_impl::WifiAddressEntryImpl>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl WifiAddressEntry {
pub fn new(
address: Option<usize>,
conn: Rc<RefCell<Connection>>,
protocol: IpProtocol,
) -> Self {
let entry: WifiAddressEntry = Object::builder().build();
let entry_imp = entry.imp();
if let Some(address) = address {
let conn = conn.borrow();
let address = unsafe { conn.ipv4.address_data.get_unchecked(address) };
entry_imp.reset_address_address.set_text(&address.address);
entry_imp
.reset_address_prefix
.set_text(&address.prefix.to_string());
entry_imp
.reset_address_row
.set_title(&format!("{}/{}", &*address.address, address.prefix));
}
entry_imp.protocol.set(protocol);
entry.setup_callbacks(conn);
entry
}
pub fn setup_callbacks(&self, connection: Rc<RefCell<Connection>>) {
let self_imp = self.imp();
let conn = connection.clone();
self_imp.reset_address_address.connect_changed(clone!(@weak self_imp => move |entry| {
let address_input = entry.text();
let mut conn = conn.borrow_mut();
if address_input.is_empty() {
self_imp.reset_address_address.remove_css_class("error");
self_imp.reset_address_row.set_title("Add new address");
return;
}
let result = match self_imp.protocol.get() {
IpProtocol::IPv4 => Ipv4Addr::from_str(address_input.as_str()).map(IpAddr::V4),
IpProtocol::IPv6 => Ipv6Addr::from_str(address_input.as_str()).map(IpAddr::V6),
};
match result {
Ok(ip_addr) => {
self_imp.reset_address_address.remove_css_class("error");
let address_data = match self_imp.protocol.get() {
IpProtocol::IPv4 => &mut conn.ipv4.address_data,
IpProtocol::IPv6 => &mut conn.ipv6.address_data,
};
address_data.push(Address::new_no_options(ip_addr.to_string(), self_imp.prefix.get().1));
*self_imp.address.borrow_mut() = (true, ip_addr.to_string());
}
Err(_) => {
self_imp.reset_address_address.add_css_class("error");
*self_imp.address.borrow_mut() = (false, String::default());
}
}
set_row_name(&self_imp);
}));
let conn = connection.clone();
self_imp.reset_address_prefix.connect_changed(clone!(@weak self_imp => move |entry| {
let prefix_input = entry.text();
let prefix = prefix_input.parse::<u8>();
let mut conn = conn.borrow_mut();
let handle_error = || {
if self_imp.reset_address_prefix.text().is_empty() {
self_imp.reset_address_prefix.remove_css_class("error");
} else {
self_imp.reset_address_prefix.add_css_class("error");
}
self_imp.prefix.set((false, 0));
set_row_name(&self_imp);
};
if prefix_input.is_empty() || prefix.is_err() {
handle_error();
return;
}
let prefix = prefix.unwrap();
match self_imp.protocol.get() {
IpProtocol::IPv4 if prefix <= 32 => {
self_imp.prefix.set((true, prefix as u32));
self_imp.reset_address_prefix.remove_css_class("error");
if let Ok(address2) = Ipv4Addr::from_str(self_imp.reset_address_address.text().as_str()) {
if let Some(addr) = conn.ipv4.address_data.iter_mut()
.find(|conn_addr| *conn_addr.address == address2.to_string()) {
addr.prefix = prefix as u32;
}
}
}
IpProtocol::IPv6 if prefix <= 128 => {
self_imp.prefix.set((true, prefix as u32));
self_imp.reset_address_prefix.remove_css_class("error");
if let Ok(address2) = Ipv6Addr::from_str(self_imp.reset_address_address.text().as_str()) {
if let Some(addr) = conn.ipv6.address_data.iter_mut()
.find(|conn_addr| *conn_addr.address == address2.to_string()) {
addr.prefix = prefix as u32;
}
}
}
_ => handle_error()
}
set_row_name(&self_imp);
}));
let conn = connection.clone();
self_imp.reset_address_remove.connect_clicked(
clone!(@weak self_imp, @weak self as what => move |_| {
let address = self_imp.reset_address_address.text();
let mut conn = conn.borrow_mut();
conn.ipv4.address_data.retain(|addr| addr.address != address);
what.unparent();
}),
);
}
}
fn set_row_name(self_imp: &WifiAddressEntryImpl) {
if self_imp.reset_address_address.text().is_empty() {
return;
}
let address = self_imp.address.borrow();
let prefix = self_imp.prefix.get();
let title = match (address.0, prefix.0) {
(true, true) => {
format!("{}/{}", address.1, prefix.1)
}
(true, false) => "Prefix wrong".to_string(),
(false, true) => "Address wrong".to_string(),
(false, false) => "Address and Prefix wrong".to_string(),
};
self_imp.reset_address_row.set_title(&title);
}

View file

@ -0,0 +1,52 @@
use crate::components::wifi::utils::IpProtocol;
use crate::components::wifi::wifi_address_entry;
use adw::{EntryRow, ExpanderRow};
use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate};
use std::cell::{Cell, RefCell};
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetWifiAddressEntry.ui")]
pub struct WifiAddressEntryImpl {
#[template_child]
pub reset_address_row: TemplateChild<ExpanderRow>,
#[template_child]
pub reset_address_address: TemplateChild<EntryRow>,
#[template_child]
pub reset_address_prefix: TemplateChild<EntryRow>,
#[template_child]
pub reset_address_remove: TemplateChild<Button>,
pub address: RefCell<(bool, String)>,
pub prefix: Cell<(bool, u32)>,
pub protocol: Cell<IpProtocol>,
}
#[glib::object_subclass]
impl ObjectSubclass for WifiAddressEntryImpl {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetWifiAddressEntry";
type Type = wifi_address_entry::WifiAddressEntry;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for WifiAddressEntryImpl {
fn constructed(&self) {
self.parent_constructed();
}
}
impl BoxImpl for WifiAddressEntryImpl {}
impl WidgetImpl for WifiAddressEntryImpl {}
impl WindowImpl for WifiAddressEntryImpl {}
impl ApplicationWindowImpl for WifiAddressEntryImpl {}

View file

@ -0,0 +1,387 @@
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use crate::components::base::error_impl::{show_error, ReSetErrorImpl};
use crate::components::base::utils::Listeners;
use crate::components::utils::{set_combo_row_ellipsis, BASE, DBUS_PATH, WIRELESS};
use adw::glib::Object;
use adw::prelude::{ComboRowExt, ListBoxRowExt, PreferencesGroupExt};
use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection;
use dbus::message::SignalArgs;
use dbus::Error;
use dbus::Path;
use glib::prelude::Cast;
use glib::property::PropertySet;
use glib::{clone, ControlFlow};
use gtk::glib::Variant;
use gtk::prelude::ActionableExt;
use gtk::{gio, StringList, StringObject};
use re_set_lib::{
network::network_structures::{AccessPoint, WifiDevice},
signals::{AccessPointAdded, WifiDeviceChanged, WifiDeviceReset},
signals::{AccessPointChanged, AccessPointRemoved},
ERROR,
};
#[cfg(debug_assertions)]
use re_set_lib::{utils::macros::ErrorLevel, write_log_to_file};
use crate::components::wifi::wifi_box_impl;
use crate::components::wifi::wifi_entry::WifiEntry;
use super::saved_wifi_entry::SavedWifiEntry;
use super::wifi_event_handlers::{
access_point_added_handler, access_point_changed_handler, access_point_removed_handler,
wifi_device_changed_handler, wifi_device_reset_handler,
};
glib::wrapper! {
pub struct WifiBox(ObjectSubclass<wifi_box_impl::WifiBox>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
type ResultMap = Result<(Vec<(Path<'static>, Vec<u8>)>,), Error>;
unsafe impl Send for WifiBox {}
unsafe impl Sync for WifiBox {}
impl ReSetErrorImpl for WifiBox {
fn error(
&self,
) -> &gtk::subclass::prelude::TemplateChild<crate::components::base::error::ReSetError> {
&self.imp().error
}
}
impl WifiBox {
pub fn new(listeners: Arc<Listeners>) -> Arc<Self> {
let obj: Arc<WifiBox> = Arc::new(Object::builder().build());
setup_callbacks(listeners, obj)
}
pub fn setup_callbacks(&self) {}
}
fn setup_callbacks(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) -> Arc<WifiBox> {
let imp = wifi_box.imp();
let wifibox_ref = wifi_box.clone();
let wifibox_ref_switch = wifi_box.clone();
imp.reset_switch_initial.set(true);
imp.reset_saved_networks.set_activatable(true);
imp.reset_saved_networks
.set_action_name(Some("navigation.push"));
imp.reset_saved_networks
.set_action_target_value(Some(&Variant::from("saved")));
imp.reset_available_networks.set_activatable(true);
imp.reset_available_networks
.set_action_name(Some("navigation.pop"));
set_combo_row_ellipsis(imp.reset_wifi_device.get());
imp.reset_wifi_switch.connect_state_set(
clone!(@weak imp => @default-return glib::Propagation::Proceed, move |_, value| {
if imp.reset_switch_initial.load(Ordering::SeqCst) {
return glib::Propagation::Proceed;
}
set_wifi_enabled(value, wifibox_ref_switch.clone());
if !value {
imp.reset_wifi_devices.write().unwrap().clear();
*imp.reset_model_list.write().unwrap() = StringList::new(&[]);
*imp.reset_model_index.write().unwrap() = 0;
let mut map = imp.wifi_entries.write().unwrap();
for entry in map.iter() {
imp.reset_wifi_list.remove(&*(*entry.1));
}
map.clear();
imp.wifi_entries_path.write().unwrap().clear();
listeners.wifi_listener.store(false, Ordering::SeqCst);
} else {
start_event_listener(listeners.clone(), wifibox_ref.clone());
show_stored_connections(wifibox_ref.clone());
scan_for_wifi(wifibox_ref.clone());
}
glib::Propagation::Proceed
}),
);
wifi_box
}
pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) {
let wifibox_ref = wifi_box.clone();
let wifi_entries = wifi_box.imp().wifi_entries.clone();
let wifi_entries_path = wifi_box.imp().wifi_entries_path.clone();
gio::spawn_blocking(move || {
let wifi_status = get_wifi_status(wifibox_ref.clone());
let devices = get_wifi_devices(wifibox_ref.clone());
if devices.is_empty() {
return;
}
let access_points = get_access_points(wifi_box.clone());
{
let imp = wifibox_ref.imp();
let list = imp.reset_model_list.write().unwrap();
let mut model_index = imp.reset_model_index.write().unwrap();
let mut map = imp.reset_wifi_devices.write().unwrap();
imp.reset_current_wifi_device
.replace(devices.last().unwrap().clone());
for (index, device) in devices.into_iter().enumerate() {
list.append(&device.name);
map.insert(device.name.clone(), (device, index as u32));
*model_index += 1;
}
}
let wifi_entries = wifi_entries.clone();
let wifi_entries_path = wifi_entries_path.clone();
dbus_start_network_events(wifibox_ref.clone());
glib::spawn_future(async move {
glib::idle_add_once(move || {
let mut wifi_entries = wifi_entries.write().unwrap();
let mut wifi_entries_path = wifi_entries_path.write().unwrap();
let imp = wifibox_ref.imp();
imp.reset_wifi_switch.set_state(wifi_status);
imp.reset_wifi_switch.set_active(wifi_status);
imp.reset_switch_initial.set(false);
let list = imp.reset_model_list.read().unwrap();
imp.reset_wifi_device.set_model(Some(&*list));
let map = imp.reset_wifi_devices.read().unwrap();
{
let device = imp.reset_current_wifi_device.borrow();
if let Some(index) = map.get(&device.name) {
imp.reset_wifi_device.set_selected(index.1);
}
}
let device_changed_ref = wifibox_ref.clone();
imp.reset_wifi_device
.connect_selected_notify(move |dropdown| {
select_wifi_device_handler(dropdown, device_changed_ref.clone());
});
for access_point in access_points {
if access_point.ssid.is_empty() {
continue;
}
let ssid = access_point.ssid.clone();
let path = access_point.dbus_path.clone();
let connected =
imp.reset_current_wifi_device.borrow().active_access_point == ssid;
let entry = WifiEntry::new(connected, access_point, imp);
wifi_entries.insert(ssid, entry.clone());
wifi_entries_path.insert(path, entry.clone());
imp.reset_wifi_list.add(&*entry);
}
});
});
});
}
fn select_wifi_device_handler(dropdown: &adw::ComboRow, wifi_box: Arc<WifiBox>) -> ControlFlow {
let selected = dropdown.selected_item();
if selected.is_none() {
return ControlFlow::Break;
}
let selected = selected.unwrap();
let selected = selected.downcast_ref::<StringObject>().unwrap();
let selected = selected.string().to_string();
let imp = wifi_box.imp();
let device = imp.reset_wifi_devices.read().unwrap();
let device = device.get(&selected);
if device.is_none() {
return ControlFlow::Break;
}
set_wifi_device(device.unwrap().0.path.clone(), wifi_box.clone());
ControlFlow::Continue
}
pub fn show_stored_connections(wifi_box: Arc<WifiBox>) {
let wifibox_ref = wifi_box.clone();
gio::spawn_blocking(move || {
let connections = get_stored_connections(wifi_box.clone());
glib::spawn_future(async move {
glib::idle_add_once(move || {
let self_imp = wifibox_ref.imp();
for connection in connections {
// FUTURE TODO: include button for settings
let name =
&String::from_utf8(connection.1).unwrap_or_else(|_| String::from(""));
let entry = SavedWifiEntry::new(name, connection.0, self_imp);
self_imp.reset_stored_wifi_list.add(&*entry);
}
});
});
});
}
pub fn dbus_start_network_events(wifi_box: Arc<WifiBox>) {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(), Error> = proxy.method_call(WIRELESS, "StartNetworkListener", ());
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to start Network listener");
}
}
pub fn get_access_points(wifi_box: Arc<WifiBox>) -> Vec<AccessPoint> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<AccessPoint>,), Error> =
proxy.method_call(WIRELESS, "ListAccessPoints", ());
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to list access points");
return Vec::new();
}
let (access_points,) = res.unwrap();
access_points
}
pub fn set_wifi_device(path: Path<'static>, wifi_box: Arc<WifiBox>) {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(bool,), Error> = proxy.method_call(WIRELESS, "SetWifiDevice", (path,));
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to set WiFi devices");
}
}
pub fn get_wifi_devices(wifi_box: Arc<WifiBox>) -> Vec<WifiDevice> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<WifiDevice>,), Error> =
proxy.method_call(WIRELESS, "GetAllWifiDevices", ());
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to get WiFi devices");
return Vec::new();
}
let (devices,) = res.unwrap();
devices
}
pub fn get_wifi_status(wifi_box: Arc<WifiBox>) -> bool {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(bool,), Error> = proxy.method_call(WIRELESS, "GetWifiStatus", ());
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to get WiFi status");
return false;
}
res.unwrap().0
}
pub fn get_stored_connections(wifi_box: Arc<WifiBox>) -> Vec<(Path<'static>, Vec<u8>)> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: ResultMap = proxy.method_call(WIRELESS, "ListStoredConnections", ());
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to list stored connections");
return Vec::new();
}
let (connections,) = res.unwrap();
connections
}
pub fn set_wifi_enabled(enabled: bool, wifi_box: Arc<WifiBox>) {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(bool,), Error> = proxy.method_call(WIRELESS, "SetWifiEnabled", (enabled,));
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to enable WiFi");
}
}
pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
gio::spawn_blocking(move || {
if listeners.wifi_disabled.load(Ordering::SeqCst)
|| listeners.wifi_listener.load(Ordering::SeqCst)
{
return;
}
listeners.wifi_listener.store(true, Ordering::SeqCst);
let conn = Connection::new_session().unwrap();
let added_ref = wifi_box.clone();
let removed_ref = wifi_box.clone();
let changed_ref = wifi_box.clone();
let wifi_changed_ref = wifi_box.clone();
let wifi_reset_ref = wifi_box.clone();
let access_point_added =
AccessPointAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let access_point_removed =
AccessPointRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let access_point_changed =
AccessPointChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let device_changed =
WifiDeviceChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let devices_reset =
WifiDeviceReset::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let res = conn.add_match(access_point_added, move |ir: AccessPointAdded, _, _| {
access_point_added_handler(added_ref.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on access point add event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(access_point_removed, move |ir: AccessPointRemoved, _, _| {
access_point_removed_handler(removed_ref.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on access point remove event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(access_point_changed, move |ir: AccessPointChanged, _, _| {
access_point_changed_handler(changed_ref.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on access point change event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(device_changed, move |ir: WifiDeviceChanged, _, _| {
wifi_device_changed_handler(wifi_changed_ref.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on wifi device change event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(devices_reset, move |ir: WifiDeviceReset, _, _| {
wifi_device_reset_handler(wifi_reset_ref.clone(), ir)
});
if res.is_err() {
ERROR!(
"fail on wifi device change event",
ErrorLevel::PartialBreakage
);
return;
}
loop {
let _ = conn.process(Duration::from_millis(1000));
if !listeners.wifi_listener.load(Ordering::SeqCst) {
break;
}
}
});
}

View file

@ -0,0 +1,83 @@
use crate::components::base::error::ReSetError;
use crate::components::wifi::wifi_box;
use adw::{ActionRow, ComboRow, NavigationView, PreferencesGroup};
use dbus::Path;
use gtk::subclass::prelude::*;
use gtk::{prelude::*, StringList};
use gtk::{CompositeTemplate, Switch};
use re_set_lib::network::network_structures::WifiDevice;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock};
use crate::components::base::list_entry::ListEntry;
use crate::components::wifi::wifi_entry::WifiEntry;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetWiFi.ui")]
pub struct WifiBox {
#[template_child]
pub reset_wifi_navigation: TemplateChild<NavigationView>,
#[template_child]
pub reset_wifi_details: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_wifi_device: TemplateChild<ComboRow>,
#[template_child]
pub reset_saved_networks: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_switch: TemplateChild<Switch>,
#[template_child]
pub reset_wifi_list: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_stored_wifi_list: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_available_networks: TemplateChild<ActionRow>,
#[template_child]
pub error: TemplateChild<ReSetError>,
pub wifi_entries: Arc<RwLock<HashMap<Vec<u8>, Arc<WifiEntry>>>>,
pub wifi_entries_path: Arc<RwLock<HashMap<Path<'static>, Arc<WifiEntry>>>>,
pub reset_wifi_devices: Arc<RwLock<HashMap<String, (WifiDevice, u32)>>>,
pub reset_current_wifi_device: Arc<RefCell<WifiDevice>>,
pub reset_model_list: Arc<RwLock<StringList>>,
pub reset_model_index: Arc<RwLock<u32>>,
pub reset_switch_initial: AtomicBool,
}
unsafe impl Send for WifiBox {}
unsafe impl Sync for WifiBox {}
#[glib::object_subclass]
impl ObjectSubclass for WifiBox {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetWifi";
type Type = wifi_box::WifiBox;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
WifiEntry::ensure_type();
ListEntry::ensure_type();
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for WifiBox {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.setup_callbacks();
}
}
impl BoxImpl for WifiBox {}
impl WidgetImpl for WifiBox {}
impl WindowImpl for WifiBox {}
impl ApplicationWindowImpl for WifiBox {}

View file

@ -0,0 +1,248 @@
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use crate::components::utils::{BASE, DBUS_PATH, WIRELESS};
use crate::components::wifi::utils::get_connection_settings;
use adw::glib::Object;
use adw::prelude::{ActionRowExt, ButtonExt, EditableExt, PopoverExt, PreferencesRowExt};
use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection;
use dbus::Error;
use glib::clone;
use glib::property::PropertySet;
use gtk::prelude::{BoxExt, ListBoxRowExt, WidgetExt};
use gtk::{gio, Align, Button, Image, Orientation};
use re_set_lib::network::network_structures::{AccessPoint, WifiStrength};
use crate::components::wifi::wifi_box_impl::WifiBox;
use crate::components::wifi::wifi_entry_impl;
use crate::components::wifi::wifi_options::WifiOptions;
glib::wrapper! {
pub struct WifiEntry(ObjectSubclass<wifi_entry_impl::WifiEntry>)
@extends adw::ActionRow, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::Actionable, gtk::ConstraintTarget, adw::PreferencesRow, gtk::ListBoxRow;
}
unsafe impl Send for WifiEntry {}
unsafe impl Sync for WifiEntry {}
impl WifiEntry {
pub fn new(connected: bool, access_point: AccessPoint, wifi_box: &WifiBox) -> Arc<Self> {
let entry: Arc<WifiEntry> = Arc::new(Object::builder().build());
let stored_entry = entry.clone();
let new_entry = entry.clone();
let entry_imp = entry.imp();
let strength = WifiStrength::from_u8(access_point.strength);
let ssid = access_point.ssid.clone();
let name_opt = String::from_utf8(ssid).unwrap_or_else(|_| String::from(""));
let name = name_opt.as_str();
entry_imp.wifi_strength.set(strength);
entry.set_title(name);
entry_imp.connected.set(connected);
entry_imp.reset_wifi_edit_button.replace(
Button::builder()
.icon_name("document-edit-symbolic")
.valign(Align::Center)
.build(),
);
// FUTURE TODO: handle encryption
let wifi_strength = Image::builder()
.icon_name(match strength {
WifiStrength::Excellent => "network-wireless-signal-excellent-symbolic",
WifiStrength::Ok => "network-wireless-signal-ok-symbolic",
WifiStrength::Weak => "network-wireless-signal-weak-symbolic",
WifiStrength::None => "network-wireless-signal-none-symbolic",
})
.build();
let prefix_box = gtk::Box::new(Orientation::Horizontal, 0);
prefix_box.append(&wifi_strength);
prefix_box.append(
&Image::builder()
.icon_name("system-lock-screen-symbolic")
.valign(Align::End)
.pixel_size(9)
.margin_bottom(12)
.build(),
);
entry.add_prefix(&prefix_box);
let suffix_box = gtk::Box::new(Orientation::Horizontal, 5);
suffix_box.append(entry_imp.reset_wifi_connected.borrow().deref());
suffix_box.append(entry_imp.reset_wifi_edit_button.borrow().deref());
entry.add_suffix(&suffix_box);
if !access_point.stored {
entry_imp
.reset_wifi_edit_button
.borrow()
.set_sensitive(false);
}
if connected {
entry_imp
.reset_wifi_connected
.borrow()
.set_text("Connected");
}
{
let mut wifi_name = entry_imp.wifi_name.borrow_mut();
*wifi_name = String::from(name);
}
entry_imp.access_point.set(access_point);
entry.set_activatable(true);
entry.connect_activated(clone!(@weak entry_imp => move |_| {
let access_point = entry_imp.access_point.borrow();
if *entry_imp.connected.borrow() {
click_disconnect(stored_entry.clone());
} else if access_point.stored {
click_stored_network(stored_entry.clone());
} else {
click_new_network(new_entry.clone());
}
}));
entry.setup_callbacks(wifi_box);
entry
}
pub fn setup_callbacks(&self, wifi_box: &WifiBox) {
let self_imp = self.imp();
self_imp.reset_wifi_edit_button.borrow().connect_clicked(clone!(@ weak self_imp, @ weak wifi_box => move |_| {
let _option = get_connection_settings(self_imp.access_point.borrow().associated_connection.clone());
wifi_box.reset_wifi_navigation.push(&*WifiOptions::new(_option, self_imp.access_point.borrow().associated_connection.clone()));
}));
}
}
pub fn click_disconnect(entry: Arc<WifiEntry>) {
let entry_ref = entry.clone();
entry_ref
.imp()
.reset_wifi_connected
.borrow()
.set_text("Disconnecting...");
entry.set_activatable(false);
gio::spawn_blocking(move || {
let imp = entry_ref.imp();
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(10000));
let res: Result<(bool,), Error> =
proxy.method_call(WIRELESS, "DisconnectFromCurrentAccessPoint", ());
if res.is_err() {
imp.connected.replace(false);
return;
}
imp.reset_wifi_connected.borrow().set_text("");
imp.connected.replace(false);
glib::spawn_future(async move {
glib::idle_add_once(move || {
entry.set_activatable(true);
});
});
});
}
pub fn click_stored_network(entry: Arc<WifiEntry>) {
let entry_imp = entry.imp();
let access_point = entry_imp.access_point.borrow().clone();
let entry_ref = entry.clone();
entry.set_sensitive(false);
entry_imp
.reset_wifi_connected
.borrow()
.set_text("Connecting...");
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(10000));
let res: Result<(bool,), Error> =
proxy.method_call(WIRELESS, "ConnectToKnownAccessPoint", (access_point,));
glib::spawn_future(async move {
glib::idle_add_once(move || {
entry.set_sensitive(true);
let imp = entry_ref.imp();
if res.is_err() {
imp.connected.replace(false);
return;
}
if res.unwrap() == (false,) {
imp.connected.replace(false);
return;
}
let imp = entry_ref.imp();
imp.reset_wifi_connected.borrow().set_text("Connected");
imp.connected.replace(true);
});
});
});
// FUTURE TODO: crate spinner animation and block UI
}
pub fn click_new_network(entry: Arc<WifiEntry>) {
let connect_new_network =
|entry: Arc<WifiEntry>, access_point: AccessPoint, password: String| {
let entry_ref = entry.clone();
let popup = entry.imp().reset_wifi_popup.imp();
popup.reset_popup_label.set_text("Connecting...");
popup.reset_popup_label.set_visible(true);
popup.reset_popup_entry.set_sensitive(false);
popup.reset_popup_button.set_sensitive(false);
entry.set_sensitive(false);
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(10000));
let res: Result<(bool,), Error> = proxy.method_call(
WIRELESS,
"ConnectToNewAccessPoint",
(access_point, password),
);
glib::spawn_future(async move {
glib::idle_add_once(move || {
entry.set_sensitive(false);
if res.is_err() {
let imp = entry_ref.imp();
imp.reset_wifi_popup
.imp()
.reset_popup_label
.set_text("Could not connect to dbus.");
imp.connected.replace(false);
return;
}
if res.unwrap() == (false,) {
let imp = entry_ref.imp();
imp.reset_wifi_popup
.imp()
.reset_popup_label
.set_text("Could not connect to access point.");
imp.connected.replace(false);
return;
}
let imp = entry_ref.imp();
imp.reset_wifi_popup.popdown();
imp.reset_wifi_edit_button.borrow().set_sensitive(true);
imp.reset_wifi_connected.borrow().set_text("Connected");
imp.connected.replace(true);
});
});
});
// FUTURE TODO: crate spinner animation and block UI
};
let entry_imp = entry.imp();
let popup_imp = entry_imp.reset_wifi_popup.imp();
popup_imp
.reset_popup_entry
.connect_activate(clone!(@weak entry as orig_entry, @weak entry_imp => move |entry| {
connect_new_network(orig_entry, entry_imp.access_point.clone().take(), entry.text().to_string());
}));
popup_imp.reset_popup_button.connect_clicked(
clone!(@weak entry as orig_entry,@weak entry_imp, @weak popup_imp => move |_| {
let entry = entry_imp.reset_wifi_popup.imp().reset_popup_entry.text().to_string();
connect_new_network(orig_entry, entry_imp.access_point.clone().take(), entry);
}),
);
entry_imp.reset_wifi_popup.popup();
}

View file

@ -0,0 +1,61 @@
use crate::components::base::popup::Popup;
use crate::components::wifi::wifi_entry;
use adw::subclass::preferences_row::PreferencesRowImpl;
use adw::subclass::prelude::ActionRowImpl;
use adw::ActionRow;
use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, Image, Label};
use re_set_lib::network::network_structures::{AccessPoint, WifiStrength};
use std::cell::RefCell;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetWifiEntry.ui")]
pub struct WifiEntry {
#[template_child]
pub reset_wifi_popup: TemplateChild<Popup>,
pub reset_wifi_strength: RefCell<Image>,
pub reset_wifi_encrypted: RefCell<Image>,
pub reset_wifi_connected: RefCell<Label>,
pub reset_wifi_edit_button: RefCell<Button>,
pub wifi_name: RefCell<String>,
pub wifi_strength: RefCell<WifiStrength>,
pub access_point: RefCell<AccessPoint>,
pub connected: RefCell<bool>,
}
unsafe impl Send for WifiEntry {}
unsafe impl Sync for WifiEntry {}
#[glib::object_subclass]
impl ObjectSubclass for WifiEntry {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetWifiEntry";
type Type = wifi_entry::WifiEntry;
type ParentType = ActionRow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for WifiEntry {
fn constructed(&self) {
self.parent_constructed();
}
}
impl PreferencesRowImpl for WifiEntry {}
impl ListBoxRowImpl for WifiEntry {}
impl ActionRowImpl for WifiEntry {}
impl WidgetImpl for WifiEntry {}
impl WindowImpl for WifiEntry {}
impl ApplicationWindowImpl for WifiEntry {}

View file

@ -0,0 +1,166 @@
use std::sync::Arc;
use adw::prelude::{ComboRowExt, PreferencesGroupExt, PreferencesRowExt};
use glib::property::PropertySet;
use glib::subclass::types::ObjectSubclassIsExt;
use gtk::prelude::WidgetExt;
use re_set_lib::{
network::network_structures::WifiStrength,
signals::{
AccessPointAdded, AccessPointChanged, AccessPointRemoved, WifiDeviceChanged,
WifiDeviceReset,
},
};
use super::{wifi_box::WifiBox, wifi_entry::WifiEntry};
pub fn access_point_added_handler(wifi_box: Arc<WifiBox>, ir: AccessPointAdded) -> bool {
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = wifi_box.imp();
let mut wifi_entries = imp.wifi_entries.write().unwrap();
let mut wifi_entries_path = imp.wifi_entries_path.write().unwrap();
let ssid = ir.access_point.ssid.clone();
let path = ir.access_point.dbus_path.clone();
if wifi_entries.get(&ssid).is_some() || ssid.is_empty() {
return;
}
let connected =
imp.reset_current_wifi_device.borrow().active_access_point == ir.access_point.ssid;
let entry = WifiEntry::new(connected, ir.access_point, imp);
wifi_entries.insert(ssid, entry.clone());
wifi_entries_path.insert(path, entry.clone());
imp.reset_wifi_list.add(&*entry);
});
});
true
}
pub fn access_point_removed_handler(wifi_box: Arc<WifiBox>, ir: AccessPointRemoved) -> bool {
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = wifi_box.imp();
let mut wifi_entries = imp.wifi_entries.write().unwrap();
let mut wifi_entries_path = imp.wifi_entries_path.write().unwrap();
let entry = wifi_entries_path.remove(&ir.access_point);
if entry.is_none() {
return;
}
let entry = entry.unwrap();
let ssid = entry.imp().access_point.borrow().ssid.clone();
wifi_entries.remove(&ssid);
imp.reset_wifi_list.remove(&*entry);
});
});
true
}
pub fn access_point_changed_handler(wifi_box: Arc<WifiBox>, ir: AccessPointChanged) -> bool {
glib::spawn_future(async move {
glib::idle_add_local_once(move || {
let imp = wifi_box.imp();
let wifi_entries = imp.wifi_entries.read().unwrap();
let entry = wifi_entries.get(&ir.access_point.ssid);
if entry.is_none() {
return;
}
let entry = entry.unwrap();
let entry_imp = entry.imp();
let strength = WifiStrength::from_u8(ir.access_point.strength);
let ssid = ir.access_point.ssid.clone();
let name_opt = String::from_utf8(ssid).unwrap_or_else(|_| String::from(""));
let name = name_opt.as_str();
entry_imp.wifi_strength.set(strength);
entry.set_title(name);
// FUTURE TODO: handle encryption thing
entry_imp
.reset_wifi_strength
.borrow()
.set_from_icon_name(match strength {
WifiStrength::Excellent => Some("network-wireless-signal-excellent-symbolic"),
WifiStrength::Ok => Some("network-wireless-signal-ok-symbolic"),
WifiStrength::Weak => Some("network-wireless-signal-weak-symbolic"),
WifiStrength::None => Some("network-wireless-signal-none-symbolic"),
});
if !ir.access_point.stored {
entry_imp
.reset_wifi_edit_button
.borrow()
.set_sensitive(false);
}
if ir.access_point.ssid == imp.reset_current_wifi_device.borrow().active_access_point {
entry_imp
.reset_wifi_connected
.borrow()
.set_text("Connected");
} else {
entry_imp.reset_wifi_connected.borrow().set_text("");
}
{
let mut wifi_name = entry_imp.wifi_name.borrow_mut();
*wifi_name = String::from(name);
}
});
});
true
}
pub fn wifi_device_changed_handler(wifi_box: Arc<WifiBox>, ir: WifiDeviceChanged) -> bool {
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = wifi_box.imp();
let mut current_device = imp.reset_current_wifi_device.borrow_mut();
if current_device.path == ir.wifi_device.path {
current_device.active_access_point = ir.wifi_device.active_access_point;
} else {
*current_device = ir.wifi_device;
}
let mut wifi_entries = imp.wifi_entries.write().unwrap();
for entry in wifi_entries.iter_mut() {
let imp = entry.1.imp();
let mut connected = imp.connected.borrow_mut();
*connected = imp.access_point.borrow().ssid == current_device.active_access_point;
if *connected {
imp.reset_wifi_connected.borrow().set_text("Connected");
} else {
imp.reset_wifi_connected.borrow().set_text("");
}
}
});
});
true
}
pub fn wifi_device_reset_handler(wifi_box: Arc<WifiBox>, ir: WifiDeviceReset) -> bool {
if ir.devices.is_empty() {
return true;
}
{
let imp = wifi_box.imp();
let list = imp.reset_model_list.write().unwrap();
let mut model_index = imp.reset_model_index.write().unwrap();
let mut map = imp.reset_wifi_devices.write().unwrap();
imp.reset_current_wifi_device
.replace(ir.devices.last().unwrap().clone());
for (index, device) in ir.devices.into_iter().enumerate() {
list.append(&device.name);
map.insert(device.name.clone(), (device, index as u32));
*model_index += 1;
}
}
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = wifi_box.imp();
let list = imp.reset_model_list.read().unwrap();
imp.reset_wifi_device.set_model(Some(&*list));
let map = imp.reset_wifi_devices.read().unwrap();
{
let device = imp.reset_current_wifi_device.borrow();
if let Some(index) = map.get(&device.name) {
imp.reset_wifi_device.set_selected(index.1);
}
}
});
});
true
}

View file

@ -0,0 +1,397 @@
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use adw::gio;
use adw::glib::Object;
use adw::prelude::{ActionRowExt, ComboRowExt, PreferencesGroupExt};
use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::arg::PropMap;
use dbus::{Error, Path};
use glib::clone;
use glib::property::PropertySet;
use gtk::prelude::{ActionableExt, ButtonExt, EditableExt, ListBoxRowExt, WidgetExt};
use re_set_lib::network::connection::{
Connection, DNSMethod4, DNSMethod6, Enum, KeyManagement, TypeSettings,
};
use IpProtocol::{IPv4, IPv6};
use crate::components::utils::{BASE, DBUS_PATH, WIRELESS};
use crate::components::wifi::utils::IpProtocol;
use crate::components::wifi::wifi_address_entry::WifiAddressEntry;
use crate::components::wifi::wifi_options_impl;
use crate::components::wifi::wifi_route_entry::WifiRouteEntry;
glib::wrapper! {
pub struct WifiOptions(ObjectSubclass<wifi_options_impl::WifiOptions>)
@extends adw::NavigationPage, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}
unsafe impl Send for WifiOptions {}
unsafe impl Sync for WifiOptions {}
impl WifiOptions {
pub fn new(connection: Connection, connection_path: Path<'static>) -> Arc<Self> {
let wifi_option: Arc<WifiOptions> = Arc::new(Object::builder().build());
wifi_option.imp().connection.set(connection);
wifi_option.initialize_ui();
setup_callbacks(&wifi_option, connection_path);
wifi_option
}
pub fn initialize_ui(&self) {
let self_imp = self.imp();
let ip4_address_length;
let ip4_route_length;
let ip6_address_length;
let ip6_route_length;
{
let conn = self_imp.connection.borrow();
ip4_address_length = conn.ipv4.address_data.len();
ip4_route_length = conn.ipv4.route_data.len();
ip6_address_length = conn.ipv4.address_data.len();
ip6_route_length = conn.ipv4.route_data.len();
// General
self_imp
.reset_wifi_auto_connect
.set_active(conn.settings.autoconnect);
self_imp
.reset_wifi_metered
.set_active(conn.settings.metered != 0);
match &conn.device {
TypeSettings::WIFI(wifi) => {
self_imp.reset_wifi_link_speed.set_visible(false);
self_imp.reset_wifi_ip4_addr.set_visible(false);
self_imp.reset_wifi_ip6_addr.set_visible(false);
self_imp.reset_wifi_dns.set_visible(false);
self_imp.reset_wifi_gateway.set_visible(false);
self_imp.reset_wifi_last_used.set_visible(true);
self_imp
.reset_wifi_mac
.set_subtitle(&wifi.cloned_mac_address);
self_imp
.reset_wifi_name
.set_subtitle(&String::from_utf8(wifi.ssid.clone()).unwrap_or_default());
}
TypeSettings::ETHERNET(ethernet) => {
self_imp.reset_wifi_link_speed.set_visible(true);
self_imp.reset_wifi_ip4_addr.set_visible(true);
self_imp.reset_wifi_ip6_addr.set_visible(true);
self_imp.reset_wifi_dns.set_visible(true);
self_imp.reset_wifi_gateway.set_visible(true);
self_imp.reset_wifi_last_used.set_visible(false);
self_imp
.reset_wifi_mac
.set_subtitle(&ethernet.cloned_mac_address);
self_imp
.reset_wifi_link_speed
.set_subtitle(&ethernet.speed.to_string());
}
TypeSettings::VPN(_vpn) => {}
TypeSettings::None => {}
};
// IPv4
self_imp
.reset_ip4_method
.set_selected(conn.ipv4.method.to_i32() as u32);
self.set_ip4_visibility(conn.ipv4.method.to_i32() as u32);
let ipv4_dns: Vec<Ipv4Addr> = conn
.ipv4
.dns
.iter()
.map(|addr| Ipv4Addr::from(*addr))
.collect();
self_imp.reset_ip4_dns.set_text(
&ipv4_dns
.iter()
.map(|ip| ip.to_string())
.collect::<Vec<String>>()
.join(", "),
);
self_imp.reset_ip4_gateway.set_text(&conn.ipv4.gateway);
// IPv6
self_imp
.reset_ip6_method
.set_selected(conn.ipv6.method.to_i32() as u32);
self.set_ip6_visibility(conn.ipv6.method.to_i32() as u32);
let ipv6_dns: Vec<String> = conn
.ipv6
.dns
.iter()
.map(|addr| {
addr.iter()
.map(|octet| octet.to_string())
.collect::<Vec<String>>()
.join(":")
})
.collect();
self_imp.reset_ip6_dns.set_text(&ipv6_dns.join(", "));
self_imp.reset_ip6_gateway.set_text(&conn.ipv6.gateway);
// Security
match &conn.security.key_management {
KeyManagement::NONE => {
self_imp.reset_wifi_security_dropdown.set_selected(0);
self_imp.reset_wifi_password.set_visible(false);
self_imp.reset_wifi_password.set_text("");
}
KeyManagement::WPAPSK => {
self_imp.reset_wifi_security_dropdown.set_selected(1);
self_imp.reset_wifi_password.set_visible(true);
self_imp.reset_wifi_password.set_text(&conn.security.psk);
}
_ => {}
}
}
// IPv4
for i in 0..ip4_address_length {
let address = &WifiAddressEntry::new(Some(i), self_imp.connection.clone(), IPv4);
self_imp.reset_ip4_address_group.add(address);
}
let address = &WifiAddressEntry::new(None, self_imp.connection.clone(), IPv4);
self_imp.reset_ip4_address_group.add(address);
for i in 0..ip4_route_length {
let route = &WifiRouteEntry::new(Some(i), self_imp.connection.clone(), IPv4);
self_imp.reset_ip4_routes_group.add(route)
}
let route = &WifiRouteEntry::new(None, self_imp.connection.clone(), IPv4);
self_imp.reset_ip4_routes_group.add(route);
// IPv6
for i in 0..ip6_address_length {
let address = &WifiAddressEntry::new(Some(i), self_imp.connection.clone(), IPv6);
self_imp.reset_ip6_address_group.add(address);
}
let address = &WifiAddressEntry::new(None, self_imp.connection.clone(), IPv6);
self_imp.reset_ip6_address_group.add(address);
for i in 0..ip6_route_length {
let route = &WifiRouteEntry::new(Some(i), self_imp.connection.clone(), IPv6);
self_imp.reset_ip6_routes_group.add(route);
}
let route = &WifiRouteEntry::new(None, self_imp.connection.clone(), IPv6);
self_imp.reset_ip6_routes_group.add(route);
}
pub fn set_ip4_visibility(&self, method: u32) {
let self_imp = self.imp();
match method {
0 => {
// auto
self_imp.reset_ip4_address_group.set_visible(false);
self_imp.reset_ip4_routes_group.set_visible(true);
self_imp.reset_ip4_gateway.set_visible(false);
}
1 => {
// manual
self_imp.reset_ip4_address_group.set_visible(true);
self_imp.reset_ip4_routes_group.set_visible(true);
self_imp.reset_ip4_gateway.set_visible(true);
}
_ => {
self_imp.reset_ip4_address_group.set_visible(false);
self_imp.reset_ip4_routes_group.set_visible(false);
self_imp.reset_ip4_gateway.set_visible(false);
}
}
}
pub fn set_ip6_visibility(&self, method: u32) {
let self_imp = self.imp();
match method {
0 | 1 => {
// auto, dhcp
self_imp.reset_ip6_address_group.set_visible(false);
self_imp.reset_ip6_routes_group.set_visible(true);
self_imp.reset_ip6_gateway.set_visible(false);
}
2 => {
// manual
self_imp.reset_ip6_address_group.set_visible(true);
self_imp.reset_ip6_routes_group.set_visible(true);
self_imp.reset_ip6_gateway.set_visible(true);
}
_ => {
self_imp.reset_ip6_address_group.set_visible(false);
self_imp.reset_ip6_routes_group.set_visible(false);
self_imp.reset_ip6_gateway.set_visible(false);
}
}
}
}
fn setup_callbacks(wifi_options: &Arc<WifiOptions>, path: Path<'static>) {
let imp = wifi_options.imp();
// General
imp.reset_wifi_auto_connect
.connect_active_notify(clone!(@weak imp => move |x| {
imp.connection.borrow_mut().settings.autoconnect = x.is_active();
}));
imp.reset_wifi_metered
.connect_active_notify(clone!(@weak imp => move |x| {
imp.connection.borrow_mut().settings.metered = if x.is_active() { 1 } else { 2 };
}));
imp.wifi_options_apply_button
.connect_clicked(clone!(@weak imp => move |_| {
let prop = imp.connection.borrow().convert_to_propmap();
set_connection_settings(path.clone(), prop);
}));
// IPv4
let wifi_options_ip4 = wifi_options.clone();
imp.reset_ip4_method
.connect_selected_notify(clone!(@weak imp => move |dropdown| {
let selected = dropdown.selected();
let mut conn = imp.connection.borrow_mut();
conn.ipv4.method = DNSMethod4::from_i32(selected as i32);
wifi_options_ip4.set_ip4_visibility(selected);
}));
imp.reset_ip4_dns
.connect_changed(clone!(@weak imp => move |entry| {
let dns_input = entry.text();
let mut conn = imp.connection.borrow_mut();
conn.ipv4.dns.clear();
if dns_input.is_empty() {
imp.reset_ip4_dns.remove_css_class("error");
return;
}
for dns_entry in dns_input.as_str().split(',').map(|s| s.trim()) {
if let Ok(addr) = Ipv4Addr::from_str(dns_entry) {
imp.reset_ip4_dns.remove_css_class("error");
conn.ipv4.dns.push(u32::from_be_bytes(addr.octets()));
} else {
imp.reset_ip4_dns.add_css_class("error");
}
}
}));
imp.reset_ip4_address_add_button
.connect_clicked(clone!(@weak imp => move |_| {
let address = &WifiAddressEntry::new(None, imp.connection.clone(), IPv4);
imp.reset_ip4_address_group.add(address);
}));
imp.reset_ip4_gateway
.connect_changed(clone!(@weak imp => move |entry| {
let gateway_input = entry.text();
let mut conn = imp.connection.borrow_mut();
conn.ipv4.gateway.clear();
if gateway_input.is_empty() {
imp.reset_ip4_gateway.remove_css_class("error");
return;
}
if Ipv4Addr::from_str(gateway_input.as_str()).is_ok() {
imp.reset_ip4_gateway.remove_css_class("error");
conn.ipv4.gateway = gateway_input.to_string();
} else {
imp.reset_ip4_gateway.add_css_class("error");
}
}));
// IPv6
let wifi_options_ip6 = wifi_options.clone();
imp.reset_ip6_method
.connect_selected_notify(clone!(@weak imp => move |dropdown| {
let selected = dropdown.selected();
let mut conn = imp.connection.borrow_mut();
conn.ipv6.method = DNSMethod6::from_i32(selected as i32);
wifi_options_ip6.set_ip6_visibility(selected);
}));
imp.reset_ip6_dns
.connect_changed(clone!(@weak imp => move |entry| {
let dns_input = entry.text();
let mut conn = imp.connection.borrow_mut();
conn.ipv6.dns.clear();
if dns_input.is_empty() {
imp.reset_ip6_dns.remove_css_class("error");
return;
}
for dns_entry in dns_input.as_str().split(',').map(|s| s.trim()) {
if let Ok(addr) = Ipv6Addr::from_str(dns_entry) {
imp.reset_ip6_dns.remove_css_class("error");
conn.ipv6.dns.push(addr.octets().to_vec());
} else {
imp.reset_ip6_dns.add_css_class("error");
}
}
}));
imp.reset_ip6_address_add_button
.connect_clicked(clone!(@weak imp => move |_| {
let address = &WifiAddressEntry::new(None, imp.connection.clone(), IPv4);
imp.reset_ip6_address_group.add(address);
}));
imp.reset_ip6_gateway
.connect_changed(clone!(@weak imp => move |entry| {
let gateway_input = entry.text();
let mut conn = imp.connection.borrow_mut();
conn.ipv6.gateway.clear();
if gateway_input.is_empty() {
imp.reset_ip6_gateway.remove_css_class("error");
return;
}
if Ipv6Addr::from_str(gateway_input.as_str()).is_ok() {
imp.reset_ip6_gateway.remove_css_class("error");
conn.ipv6.gateway = gateway_input.to_string();
} else {
imp.reset_ip6_gateway.add_css_class("error");
}
}));
// Security
imp.reset_wifi_security_dropdown
.connect_selected_notify(clone!(@weak imp => move |dropdown| {
let selected = dropdown.selected();
let mut conn = imp.connection.borrow_mut();
match selected {
0 => { // None
imp.reset_wifi_password.set_visible(false);
conn.security.key_management = KeyManagement::NONE;
conn.security.authentication_algorithm = String::from("none");
},
1 => { // WPA/WPA2 Personal
imp.reset_wifi_password.set_visible(true);
conn.security.key_management = KeyManagement::WPAPSK;
conn.security.authentication_algorithm = String::from("none");
},
_ => {}
}
}));
imp.reset_wifi_password
.connect_changed(clone!(@weak imp => move |entry| {
let password_input = entry.text();
if password_input.len() < 8 && !password_input.is_empty() {
entry.add_css_class("error");
} else {
entry.remove_css_class("error");
}
let mut conn = imp.connection.borrow_mut();
conn.security.psk = password_input.to_string();
}));
imp.reset_available_networks.set_activatable(true);
imp.reset_available_networks
.set_action_name(Some("navigation.pop"));
}
fn set_connection_settings(path: Path<'static>, prop: HashMap<String, PropMap>) {
gio::spawn_blocking(move || {
let conn = dbus::blocking::Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let _: Result<(bool,), Error> =
proxy.method_call(WIRELESS, "SetConnectionSettings", (path, prop));
});
}

View file

@ -0,0 +1,111 @@
use crate::components::wifi::wifi_options;
use adw::subclass::prelude::NavigationPageImpl;
use adw::{
ActionRow, ComboRow, EntryRow, NavigationPage, PasswordEntryRow, PreferencesGroup, SwitchRow,
};
use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, Label};
use re_set_lib::network::connection::Connection;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetWifiOptions.ui")]
pub struct WifiOptions {
// General
#[template_child]
pub reset_wifi_name: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_mac: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_link_speed: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_ip4_addr: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_ip6_addr: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_gateway: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_dns: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_last_used: TemplateChild<ActionRow>,
#[template_child]
pub reset_wifi_auto_connect: TemplateChild<SwitchRow>,
#[template_child]
pub reset_wifi_metered: TemplateChild<SwitchRow>,
// IPv4
#[template_child]
pub reset_ip4_method: TemplateChild<ComboRow>,
#[template_child]
pub reset_ip4_dns: TemplateChild<EntryRow>,
#[template_child]
pub reset_ip4_gateway: TemplateChild<EntryRow>,
#[template_child]
pub reset_ip4_address_group: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_ip4_address_add_button: TemplateChild<Button>,
#[template_child]
pub reset_ip4_routes_group: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_ip4_route_add_button: TemplateChild<Button>,
// IPv6
#[template_child]
pub reset_ip6_method: TemplateChild<ComboRow>,
#[template_child]
pub reset_ip6_dns: TemplateChild<EntryRow>,
#[template_child]
pub reset_ip6_gateway: TemplateChild<EntryRow>,
#[template_child]
pub reset_ip6_address_group: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_ip6_address_add_button: TemplateChild<Button>,
#[template_child]
pub reset_ip6_routes_group: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_ip6_route_add_button: TemplateChild<Button>,
// Security
#[template_child]
pub reset_wifi_security_dropdown: TemplateChild<ComboRow>,
#[template_child]
pub reset_wifi_password: TemplateChild<PasswordEntryRow>,
// Misc
#[template_child]
pub reset_available_networks: TemplateChild<ActionRow>,
#[template_child]
pub wifi_options_apply_button: TemplateChild<Button>,
#[template_child]
pub wifi_options_error_msg: TemplateChild<Label>,
pub connection: Rc<RefCell<Connection>>,
}
#[glib::object_subclass]
impl ObjectSubclass for WifiOptions {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetWifiOptions";
type Type = wifi_options::WifiOptions;
type ParentType = NavigationPage;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl NavigationPageImpl for WifiOptions {}
impl ObjectImpl for WifiOptions {
fn constructed(&self) {
self.parent_constructed();
}
}
impl BoxImpl for WifiOptions {}
impl WidgetImpl for WifiOptions {}
impl WindowImpl for WifiOptions {}
impl ApplicationWindowImpl for WifiOptions {}

View file

@ -0,0 +1,240 @@
use crate::components::wifi::utils::IpProtocol;
use adw::glib::Object;
use adw::prelude::{ExpanderRowExt, PreferencesRowExt};
use glib::clone;
use glib::subclass::prelude::ObjectSubclassIsExt;
use gtk::prelude::{EditableExt, WidgetExt};
use re_set_lib::network::connection::{Address, Connection};
use std::cell::RefCell;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::rc::Rc;
use std::str::FromStr;
use crate::components::wifi::wifi_route_entry_impl;
use crate::components::wifi::wifi_route_entry_impl::WifiRouteEntryImpl;
glib::wrapper! {
pub struct WifiRouteEntry(ObjectSubclass<wifi_route_entry_impl::WifiRouteEntryImpl>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl WifiRouteEntry {
pub fn new(
address: Option<usize>,
conn: Rc<RefCell<Connection>>,
protocol: IpProtocol,
) -> Self {
let entry: WifiRouteEntry = Object::builder().build();
let entry_imp = entry.imp();
if let Some(address) = address {
let conn = conn.borrow();
let address = unsafe { conn.ipv4.route_data.get_unchecked(address) };
entry_imp.reset_route_address.set_text(&address.address);
entry_imp
.reset_route_prefix
.set_text(&address.prefix.to_string());
if let Some(gateway) = &address.gateway {
entry_imp.reset_route_gateway.set_text(gateway);
}
if let Some(metric) = address.metric {
entry_imp.reset_route_metric.set_text(&metric.to_string());
}
entry_imp
.reset_route_row
.set_title(&format!("{}/{}", &*address.address, address.prefix));
}
entry_imp.protocol.set(protocol);
entry.setup_callbacks(conn);
entry
}
fn setup_callbacks(&self, connection: Rc<RefCell<Connection>>) {
let self_imp = self.imp();
let conn = connection.clone();
self_imp.reset_route_address.connect_changed(clone!(@weak self_imp => move |entry| {
let address_input = entry.text();
let mut conn = conn.borrow_mut();
if address_input.is_empty() {
self_imp.reset_route_address.remove_css_class("error");
self_imp.reset_route_row.set_title("Add new address");
return;
}
let result = match self_imp.protocol.get() {
IpProtocol::IPv4 => Ipv4Addr::from_str(address_input.as_str()).map(IpAddr::V4),
IpProtocol::IPv6 => Ipv6Addr::from_str(address_input.as_str()).map(IpAddr::V6),
};
match result {
Ok(ip_addr) => {
self_imp.reset_route_address.remove_css_class("error");
let address_data = match self_imp.protocol.get() {
IpProtocol::IPv4 => &mut conn.ipv4.route_data,
IpProtocol::IPv6 => &mut conn.ipv6.route_data,
};
address_data.push(Address::new(ip_addr.to_string(), self_imp.prefix.get().1 as u32,self_imp.gateway.borrow().clone() ,self_imp.metric.get()));
*self_imp.address.borrow_mut() = (true, ip_addr.to_string());
}
Err(_) => {
self_imp.reset_route_address.add_css_class("error");
*self_imp.address.borrow_mut() = (false, String::default());
}
}
set_row_title(&self_imp);
}));
let conn = connection.clone();
self_imp.reset_route_prefix.connect_changed(clone!(@weak self_imp => move |entry| {
let prefix_input = entry.text();
let prefix = prefix_input.parse::<u8>();
let mut conn = conn.borrow_mut();
let handle_error = || {
if self_imp.reset_route_prefix.text().is_empty() {
self_imp.reset_route_prefix.remove_css_class("error");
} else {
self_imp.reset_route_prefix.add_css_class("error");
}
self_imp.prefix.set((false, 0));
set_row_title(&self_imp);
};
if prefix_input.is_empty() || prefix.is_err() {
handle_error();
return;
}
let prefix = prefix.unwrap();
match self_imp.protocol.get() {
IpProtocol::IPv4 if prefix <= 32 => {
self_imp.prefix.set((true, prefix as u32));
self_imp.reset_route_prefix.remove_css_class("error");
if let Ok(address2) = Ipv4Addr::from_str(self_imp.reset_route_address.text().as_str()) {
if let Some(addr) = conn.ipv4.route_data.iter_mut()
.find(|conn_addr| *conn_addr.address == address2.to_string()) {
addr.prefix = prefix as u32;
}
}
}
IpProtocol::IPv6 if prefix <= 128 => {
self_imp.prefix.set((true, prefix as u32));
self_imp.reset_route_prefix.remove_css_class("error");
if let Ok(address2) = Ipv6Addr::from_str(self_imp.reset_route_address.text().as_str()) {
if let Some(addr) = conn.ipv6.route_data.iter_mut()
.find(|conn_addr| *conn_addr.address == address2.to_string()) {
addr.prefix = prefix as u32;
}
}
}
_ => handle_error()
}
set_row_title(&self_imp);
}));
let conn = connection.clone();
self_imp
.reset_route_gateway
.connect_changed(clone!(@weak self_imp => move |entry| {
let gateway_input = entry.text();
let mut conn = conn.borrow_mut();
if gateway_input.is_empty() {
self_imp.reset_route_gateway.remove_css_class("error");
*self_imp.gateway.borrow_mut() = None;
set_row_subtitle(&self_imp);
return;
}
let result = match self_imp.protocol.get() {
IpProtocol::IPv4 => Ipv4Addr::from_str(gateway_input.as_str()).map(IpAddr::V4),
IpProtocol::IPv6 => Ipv6Addr::from_str(gateway_input.as_str()).map(IpAddr::V6),
};
match result {
Ok(ip_addr) => {
self_imp.reset_route_gateway.remove_css_class("error");
let address_data = match self_imp.protocol.get() {
IpProtocol::IPv4 => &mut conn.ipv4.route_data,
IpProtocol::IPv6 => &mut conn.ipv6.route_data,
};
if let Some(address) = address_data.iter_mut()
.find(|conn_addr| *conn_addr.address == self_imp.reset_route_address.text()) {
address.gateway = Some(ip_addr.to_string());
}
*self_imp.gateway.borrow_mut() = Some(ip_addr.to_string());
}
Err(_) => {
self_imp.reset_route_gateway.add_css_class("error");
*self_imp.gateway.borrow_mut() = None;
}
}
set_row_subtitle(&self_imp);
}));
let conn = connection.clone();
self_imp
.reset_route_metric
.connect_changed(clone!(@weak self_imp => move |entry| {
let metric_input = entry.text();
let mut conn = conn.borrow_mut();
if metric_input.is_empty() {
self_imp.reset_route_metric.remove_css_class("error");
self_imp.metric.set(None);
set_row_subtitle(&self_imp);
return;
}
let result = metric_input.parse::<u32>();
match result {
Ok(metric) => {
self_imp.reset_route_metric.remove_css_class("error");
let address_data = match self_imp.protocol.get() {
IpProtocol::IPv4 => &mut conn.ipv4.route_data,
IpProtocol::IPv6 => &mut conn.ipv6.route_data,
};
if let Some(address) = address_data.iter_mut()
.find(|conn_addr| *conn_addr.address == self_imp.reset_route_address.text()) {
address.metric = Some(metric);
}
self_imp.metric.set(Some(metric));
}
Err(_) => {
self_imp.reset_route_metric.add_css_class("error");
self_imp.metric.set(None);
}
}
set_row_subtitle(&self_imp);
}));
}
}
fn set_row_title(self_imp: &WifiRouteEntryImpl) {
if self_imp.reset_route_address.text().is_empty() {
return;
}
let address = self_imp.address.borrow();
let prefix = self_imp.prefix.get();
let title = match (address.0, prefix.0) {
(true, true) => {
format!("{}/{}", address.1, prefix.1)
}
(true, false) => "Prefix wrong".to_string(),
(false, true) => "Address wrong".to_string(),
(false, false) => "Address and Prefix wrong".to_string(),
};
self_imp.reset_route_row.set_title(&title);
}
fn set_row_subtitle(self_imp: &WifiRouteEntryImpl) {
let gateway = self_imp.gateway.borrow().clone();
let metric = self_imp.metric.get();
let title = match (gateway, metric) {
(Some(gateway), Some(metric)) => {
format!("{}, {}", gateway, metric)
}
(Some(gateway), None) => gateway,
(None, Some(metric)) => metric.to_string(),
(None, None) => String::default(),
};
self_imp.reset_route_row.set_subtitle(&title);
}

View file

@ -0,0 +1,58 @@
use crate::components::wifi::utils::IpProtocol;
use crate::components::wifi::wifi_route_entry;
use adw::{EntryRow, ExpanderRow};
use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate};
use std::cell::{Cell, RefCell};
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetWifiRouteEntry.ui")]
pub struct WifiRouteEntryImpl {
#[template_child]
pub reset_route_row: TemplateChild<ExpanderRow>,
#[template_child]
pub reset_route_address: TemplateChild<EntryRow>,
#[template_child]
pub reset_route_prefix: TemplateChild<EntryRow>,
#[template_child]
pub reset_route_gateway: TemplateChild<EntryRow>,
#[template_child]
pub reset_route_metric: TemplateChild<EntryRow>,
#[template_child]
pub reset_route_remove: TemplateChild<Button>,
pub address: RefCell<(bool, String)>,
pub prefix: Cell<(bool, u32)>,
pub gateway: RefCell<Option<String>>,
pub metric: Cell<Option<u32>>,
pub protocol: Cell<IpProtocol>,
}
#[glib::object_subclass]
impl ObjectSubclass for WifiRouteEntryImpl {
const ABSTRACT: bool = false;
const NAME: &'static str = "resetWifiRouteEntry";
type Type = wifi_route_entry::WifiRouteEntry;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for WifiRouteEntryImpl {
fn constructed(&self) {
self.parent_constructed();
}
}
impl BoxImpl for WifiRouteEntryImpl {}
impl WidgetImpl for WifiRouteEntryImpl {}
impl WindowImpl for WifiRouteEntryImpl {}
impl ApplicationWindowImpl for WifiRouteEntryImpl {}

View file

@ -0,0 +1,48 @@
use crate::components::plugin::function::ReSetSidebarInfo;
use super::handle_sidebar_click::{
HANDLE_AUDIO_CLICK, HANDLE_BLUETOOTH_CLICK, HANDLE_CONNECTIVITY_CLICK, HANDLE_MICROPHONE_CLICK,
HANDLE_VOLUME_CLICK, HANDLE_WIFI_CLICK,
};
pub const CONNECTIVITY_SIDEBAR: ReSetSidebarInfo = ReSetSidebarInfo {
name: "Connectivity",
icon_name: "network-wired-symbolic",
parent: None,
click_event: HANDLE_CONNECTIVITY_CLICK,
};
pub const WIFI_SIDEBAR: ReSetSidebarInfo = ReSetSidebarInfo {
name: "WiFi",
icon_name: "network-wireless-symbolic",
parent: Some("Connectivity"),
click_event: HANDLE_WIFI_CLICK,
};
pub const BLUETOOTH_SIDEBAR: ReSetSidebarInfo = ReSetSidebarInfo {
name: "Bluetooth",
icon_name: "bluetooth-symbolic",
parent: Some("Connectivity"),
click_event: HANDLE_BLUETOOTH_CLICK,
};
pub const AUDIO_SIDEBAR: ReSetSidebarInfo = ReSetSidebarInfo {
name: "Audio",
icon_name: "audio-headset-symbolic",
parent: None,
click_event: HANDLE_AUDIO_CLICK,
};
pub const SINK_SIDEBAR: ReSetSidebarInfo = ReSetSidebarInfo {
name: "Output",
icon_name: "audio-volume-high-symbolic",
parent: Some("Audio"),
click_event: HANDLE_VOLUME_CLICK,
};
pub const SOURCE_SIDEBAR: ReSetSidebarInfo = ReSetSidebarInfo {
name: "Input",
icon_name: "audio-input-microphone-symbolic",
parent: Some("Audio"),
click_event: HANDLE_MICROPHONE_CLICK,
};

Some files were not shown because too many files have changed in this diff Show more