Compare commits

...

114 commits
0.1.8 ... 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
128 changed files with 10203 additions and 3921 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,25 +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
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:
nix_path: nixpkgs=channel:nixos-unstable
- name: cache
uses: cachix/cachix-action@v14
with:
name: reset
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build
run: cargo build --verbose
- name: Run clippy
run: cargo clippy --fix
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,19 +1,23 @@
[package]
name = "reset"
version = "0.1.8"
version = "2.0.0"
edition = "2021"
description = "A wip universal Linux settings application."
repository = "https://github.com/Xetibo/ReSet"
license = "GPL-3.0-only"
license = "GPL-3.0-or-later"
[[bin]]
name = "ReSet"
path = "src/main.rs"
[dependencies]
reset_daemon = "0.7.2"
re_set-lib = "0.8.7"
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"] }
glib = "0.18.3"
tokio = { version = "1.33.0", features = [
gtk = { version = "0.8.1", package = "gtk4", features = ["v4_12"] }
glib = "0.19.3"
tokio = { version = "1.36.0", features = [
"rt",
"time",
"net",
@ -21,8 +25,8 @@ tokio = { version = "1.33.0", features = [
"rt-multi-thread",
"sync",
] }
fork = "0.1.22"
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.8
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"
}

View file

@ -5,11 +5,11 @@
![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
@ -23,31 +23,58 @@ A window manager/compositor agnostic settings application for Linux written in r
<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 from the release and install with the terminal.
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 from the releases tab and install it with pacman.
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
@ -55,31 +82,60 @@ sudo pacman -U /path/to/reset
### Debian Package(Ubuntu 23.04 dependencies)
Download the package from the releases tab and install it with apt.
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
This application was developed as a semester project for the Eastern Switzerland University of Applied Sciences.
With potential advancements as a next project, due to this, no major development will happen until February 2024.
However, there is still a roadmap for this application.
## Roadmap and Notes
- Plugin System
- 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;

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;
};
};
};
}

View file

@ -1,4 +1,3 @@
#! /bin/bash
python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json
flatpak-builder build org.Xetibo.ReSet.json --force-clean
flatpak build-export export build

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,13 @@
{
"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"
"org.freedesktop.Sdk.Extension.rust-nightly"
],
"command": "reset",
"command": "ReSet",
"finish-args": [
"--system-talk-name=org.freedesktop.NetworkManager",
"--system-talk-name=org.bluez",
"--socket=pulseaudio",
"--share=network",
"--share=ipc",
@ -17,10 +15,12 @@
"--socket=wayland",
"--device=dri",
"--device=all",
"--allow=bluetooth"
"--allow=bluetooth",
"--socket=system-bus",
"--socket=session-bus"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin"
"append-path": "/usr/lib/sdk/rust-nightly/bin"
},
"modules": [
{
@ -33,8 +33,8 @@
},
"build-commands": [
"cargo --offline fetch --manifest-path Cargo.toml --verbose",
"cargo --offline build --release --verbose",
"install -Dm755 ./target/release/reset -t /app/bin/",
"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"
],

View file

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

View file

@ -1,12 +1,12 @@
{
"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"
"org.freedesktop.Sdk.Extension.rust-nightly"
],
"command": "reset",
"command": "ReSet",
"finish-args": [
"--socket=pulseaudio",
"--share=network",
@ -17,10 +17,11 @@
"--device=all",
"--allow=bluetooth",
"--socket=system-bus",
"--socket=session-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": [
{
@ -34,7 +35,7 @@
"build-commands": [
"cargo --offline fetch --manifest-path Cargo.toml --verbose",
"cargo --offline build --release --verbose",
"install -Dm755 ./target/release/reset -t /app/bin/",
"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"
],

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

@ -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

@ -2,5 +2,6 @@ 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

@ -5,13 +5,16 @@ 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 crate::components::input::source_box;
use gtk::subclass::prelude::*;
use gtk::{glib, CheckButton, CompositeTemplate, StringList, TemplateChild};
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)>>>;
@ -34,7 +37,6 @@ pub struct SourceBox {
pub reset_volume_slider: TemplateChild<Scale>,
#[template_child]
pub reset_volume_percentage: TemplateChild<Label>,
#[template_child]
pub reset_sources: TemplateChild<gtk::Box>,
#[template_child]
@ -45,6 +47,8 @@ pub struct SourceBox {
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,
@ -76,12 +80,7 @@ impl ObjectSubclass for SourceBox {
impl BoxImpl for SourceBox {}
impl ObjectImpl for SourceBox {
fn constructed(&self) {
let obj = self.obj();
obj.setup_callbacks();
}
}
impl ObjectImpl for SourceBox {}
impl ListBoxRowImpl for SourceBox {}
@ -90,3 +89,93 @@ 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

@ -6,8 +6,11 @@ use std::sync::Arc;
use std::time::SystemTime;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CheckButton, CompositeTemplate, Label, Scale};
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)]
@ -48,3 +51,49 @@ 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

@ -0,0 +1,8 @@
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

@ -2,5 +2,6 @@ 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

@ -5,13 +5,16 @@ 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 crate::components::output::input_stream_entry::InputStreamEntry;
use gtk::subclass::prelude::*;
use gtk::{glib, Box, Button, CheckButton, CompositeTemplate, Label, StringList, TemplateChild};
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)>>>;
@ -45,6 +48,8 @@ pub struct SinkBox {
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,
@ -76,12 +81,7 @@ impl ObjectSubclass for SinkBox {
impl BoxImpl for SinkBox {}
impl ObjectImpl for SinkBox {
fn constructed(&self) {
let obj = self.obj();
obj.setup_callbacks();
}
}
impl ObjectImpl for SinkBox {}
impl ListBoxRowImpl for SinkBox {}
@ -90,3 +90,93 @@ 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

@ -5,9 +5,12 @@ use std::cell::RefCell;
use std::sync::Arc;
use std::time::SystemTime;
use crate::components::output::sink_entry;
use crate::components::audio::audio_entry::{AudioIcons, DBusFunction, TAudioEntryImpl};
use crate::components::audio::output::sink_entry;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CheckButton, CompositeTemplate, Label, Scale};
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")]
@ -22,7 +25,7 @@ pub struct SinkEntry {
pub reset_volume_slider: TemplateChild<Scale>,
#[template_child]
pub reset_volume_percentage: TemplateChild<Label>,
pub stream: Arc<RefCell<Sink>>,
pub sink: Arc<RefCell<Sink>>,
pub volume_time_stamp: RefCell<Option<SystemTime>>,
}
@ -47,3 +50,49 @@ 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

@ -1,19 +1,19 @@
use std::time::Duration;
use adw::glib;
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 glib::{clone, Cast};
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::{BASE, DBUS_PATH, AUDIO};
use crate::components::utils::{AUDIO, BASE, DBUS_PATH};
use super::card_entry_impl;
@ -66,11 +66,7 @@ impl CardEntry {
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 proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let _: Result<(), Error> = proxy.method_call(
AUDIO,
"SetCardProfileOfDevice",

View file

@ -6,7 +6,7 @@ use std::cell::RefCell;
use std::collections::HashMap;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate};
use gtk::CompositeTemplate;
use super::card_entry;

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

@ -1,6 +1,6 @@
use crate::components::base::list_entry_impl;
use adw::glib;
use adw::glib::{IsA, Object};
use adw::glib::Object;
use glib::prelude::IsA;
use gtk::prelude::ListBoxRowExt;
use gtk::Widget;

View file

@ -1,6 +1,6 @@
use crate::components::base::list_entry;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate};
use gtk::CompositeTemplate;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetListBoxRow.ui")]

View file

@ -1,5 +1,7 @@
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;

View file

@ -1,4 +1,3 @@
use adw::glib;
use adw::glib::Object;
use gtk::{gdk, Editable, Popover};

View file

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::sync::Arc;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Label, PasswordEntry, PasswordEntryBuffer, Popover};
use gtk::{Button, CompositeTemplate, Label, PasswordEntry, PasswordEntryBuffer, Popover};
use super::popup;

View file

@ -1,12 +1,12 @@
use crate::components::base::setting_box_impl;
use adw::glib;
use adw::glib::{IsA, Object};
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, gtk::Widget,
@extends gtk::Box, Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}

View file

@ -1,6 +1,6 @@
use crate::components::base::setting_box;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate};
use gtk::CompositeTemplate;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetSettingBox.ui")]

View file

@ -11,8 +11,8 @@ use dbus::{blocking::Connection, Error};
use gtk::gio;
use crate::components::{
input::source_box::{start_input_box_listener, SourceBox},
output::sink_box::{start_output_box_listener, SinkBox},
audio::input::source_box::{start_source_box_listener, SourceBox},
audio::output::sink_box::{start_sink_box_listener, SinkBox},
utils::{BASE, DBUS_PATH, WIRELESS},
};
@ -24,6 +24,7 @@ pub enum Position {
Audio,
AudioOutput,
AudioInput,
Custom(String),
#[default]
Home,
}
@ -71,10 +72,10 @@ pub fn start_audio_listener(
}
if let Some(sink_box) = sink_box {
conn = start_output_box_listener(conn, sink_box);
conn = start_sink_box_listener(conn, sink_box);
}
if let Some(source_box) = source_box {
conn = start_input_box_listener(conn, source_box);
conn = start_source_box_listener(conn, source_box);
}
listeners.pulse_listener.store(true, Ordering::SeqCst);

View file

@ -2,25 +2,37 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use adw::glib;
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::{clone, Cast, PropertySet};
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};
use re_set_lib::signals::{BluetoothDeviceAdded, BluetoothDeviceChanged, BluetoothDeviceRemoved};
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,
@ -30,6 +42,14 @@ glib::wrapper! {
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());
@ -37,7 +57,7 @@ impl BluetoothBox {
}
}
// TODO
// FUTURE TODO:
// handle bonded -> this means saved but not connected
// handle rssi below x -> don't show device
@ -68,85 +88,107 @@ fn setup_callbacks(
.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());
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());
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(
clone!(@weak imp => @default-return glib::Propagation::Proceed, move |_, state| {
if imp.reset_switch_initial.load(Ordering::SeqCst) {
return glib::Propagation::Proceed;
}
if !state {
let imp = bluetooth_box_ref.imp();
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,
);
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,
) {
current_adapter.powered = true;
start_bluetooth_listener(restart_listener_ref.clone(), restart_ref.clone());
}
});
}
glib::Propagation::Proceed
}),
);
imp.reset_bluetooth_switch
.connect_state_set(move |_, state| {
bluetooth_enabled_switch_handler(
state,
bluetooth_box_ref.clone(),
listeners_ref.clone(),
)
});
bluetooth_box
}
pub fn populate_conntected_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) {
// TODO handle saved devices -> they also exist
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 devices = get_connected_devices();
let adapters = get_bluetooth_adapters();
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();
@ -163,8 +205,10 @@ pub fn populate_conntected_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) {
*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();
@ -187,36 +231,31 @@ pub fn populate_conntected_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) {
imp.reset_switch_initial.set(false);
}
imp.reset_bluetooth_adapter.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 device = imp.reset_bluetooth_adapters.read().unwrap();
let device = device.get(&selected);
if device.is_none() {
return;
}
set_bluetooth_adapter(device.unwrap().0.path.clone());
}),
);
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 bluetooth_entry = BluetoothEntry::new(device);
imp.available_devices
.borrow_mut()
.insert(path, bluetooth_entry.clone());
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());
}
}
});
@ -224,6 +263,28 @@ pub fn populate_conntected_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) {
});
}
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) {
@ -242,7 +303,10 @@ pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<Bl
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let _: Result<(), Error> = proxy.method_call(BLUETOOTH, "StartBluetoothListener", ());
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 =
@ -256,197 +320,205 @@ pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<Bl
.static_clone();
let res = conn.add_match(device_added, move |ir: BluetoothDeviceAdded, _, _| {
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 connected = ir.bluetooth_device.connected;
let bluetooth_entry = BluetoothEntry::new(ir.bluetooth_device);
imp.available_devices
.borrow_mut()
.insert(path, bluetooth_entry.clone());
if connected {
imp.reset_bluetooth_connected_devices.add(&*bluetooth_entry);
} else {
imp.reset_bluetooth_available_devices.add(&*bluetooth_entry);
}
});
});
true
device_added_handler(device_added_box.clone(), ir)
});
if res.is_err() {
println!("fail on bluetooth device add event");
ERROR!(
"fail on bluetooth device add event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(device_removed, move |ir: BluetoothDeviceRemoved, _, _| {
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 {
imp.reset_bluetooth_available_devices.remove(&*list_entry);
}
}
});
});
true
device_removed_handler(device_removed_box.clone(), ir)
});
if res.is_err() {
println!("fail on bluetooth device remove event");
ERROR!(
"fail on bluetooth device remove event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(device_changed, move |ir: BluetoothDeviceChanged, _, _| {
let bluetooth_box = device_changed_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.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_connected_devices.add(&**list_entry);
} else {
imp.reset_bluetooth_connected_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
device_changed_handler(device_changed_box.clone(), ir)
});
if res.is_err() {
println!("fail on bluetooth device remove event");
ERROR!(
"fail on bluetooth device remove event",
ErrorLevel::PartialBreakage
);
return;
}
listeners.bluetooth_listener.store(true, Ordering::SeqCst);
let mut time = SystemTime::now();
let mut listener_active = true;
let time = SystemTime::now();
let listener_active = true;
loop {
let _ = conn.process(Duration::from_millis(1000));
if !listeners.bluetooth_listener.load(Ordering::SeqCst) {
let _: Result<(), Error> =
proxy.method_call(BLUETOOTH, "StopBluetoothListener", ());
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 _: Result<(), Error> = proxy.method_call(BLUETOOTH, "StopBluetoothScan", ());
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 _: Result<(), Error> =
proxy.method_call(BLUETOOTH, "StartBluetoothListener", ());
loop_box
.imp()
.reset_bluetooth_available_devices
.set_description(Some("Scanning..."));
time = SystemTime::now();
}
}
bluetooth_listener_loop(
&conn,
listeners,
proxy,
bluetooth_box,
loop_box,
listener_active,
time,
);
});
}
fn get_connected_devices() -> Vec<BluetoothDevice> {
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, "GetConnectedBluetoothDevices", ());
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() -> Vec<BluetoothAdapter> {
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>) {
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 _: Result<(Path<'static>,), Error> =
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) {
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 _: Result<(bool,), Error> = proxy.method_call(
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) {
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 _: Result<(bool,), Error> =
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) -> bool {
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

@ -1,14 +1,15 @@
use adw::{ActionRow, ComboRow, PreferencesGroup, SwitchRow};
use dbus::Path;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Switch};
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;
@ -23,6 +24,8 @@ pub struct BluetoothBox {
#[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>,
@ -36,8 +39,11 @@ pub struct BluetoothBox {
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>>,

View file

@ -2,11 +2,12 @@ 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, DBUS_PATH, BLUETOOTH};
use crate::components::utils::{BASE, BLUETOOTH, DBUS_PATH};
use adw::glib::Object;
use adw::prelude::{ActionRowExt, PreferencesRowExt};
use adw::{glib, ActionRow};
use adw::ActionRow;
use dbus::blocking::Connection;
use dbus::{Error, Path};
use glib::subclass::prelude::ObjectSubclassIsExt;
@ -14,6 +15,8 @@ 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,
@ -24,7 +27,7 @@ unsafe impl Send for BluetoothEntry {}
unsafe impl Sync for BluetoothEntry {}
impl BluetoothEntry {
pub fn new(device: BluetoothDevice) -> Arc<Self> {
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();
@ -60,11 +63,14 @@ impl BluetoothEntry {
.borrow()
.connect_clicked(move |_| {
let imp = entry_ref_remove.imp();
remove_device_pairing(imp.bluetooth_device.borrow().path.clone());
remove_device_pairing(
imp.bluetooth_device.borrow().path.clone(),
bluetooth_box.clone(),
);
});
let gesture = GestureClick::new();
// paired is not what we think
// TODO implement paired
// FUTURE TODO: implement paired
gesture.connect_released(move |_, _, _, _| {
let imp = entry_ref.imp();
let borrow = imp.bluetooth_device.borrow();
@ -87,16 +93,9 @@ impl BluetoothEntry {
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,),
);
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() {
@ -134,16 +133,9 @@ fn connect_to_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
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,),
);
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();
@ -161,15 +153,14 @@ fn disconnect_from_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
});
}
fn remove_device_pairing(path: Path<'static>) {
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 _: Result<(bool,), Error> =
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

@ -3,7 +3,7 @@ use adw::subclass::action_row::ActionRowImpl;
use adw::subclass::preferences_row::PreferencesRowImpl;
use adw::ActionRow;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Label};
use gtk::{Button, CompositeTemplate, Label};
use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice;
use std::cell::RefCell;

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

@ -2,3 +2,4 @@ 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,193 +0,0 @@
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use crate::components::utils::{
create_dropdown_label_factory, set_combo_row_ellipsis, AUDIO, BASE, DBUS_PATH,
};
use adw::glib;
use adw::glib::Object;
use adw::prelude::{ButtonExt, ComboRowExt, PreferencesRowExt, RangeExt};
use dbus::blocking::Connection;
use dbus::Error;
use glib::subclass::types::ObjectSubclassIsExt;
use glib::{clone, Cast, Propagation};
use gtk::{gio, StringObject};
use re_set_lib::audio::audio_structures::OutputStream;
use super::output_stream_entry_impl;
use super::source_box::SourceBox;
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 OutputStreamEntry {
pub fn new(source_box: Arc<SourceBox>, stream: OutputStream) -> Self {
let obj: Self = Object::builder().build();
// TODO use event callback for progress bar -> this is the "im speaking" indicator
{
let index = stream.index;
let box_imp = source_box.imp();
let imp = obj.imp();
let name = stream.application_name.clone() + ": " + stream.name.as_str();
imp.reset_source_selection.set_title(name.as_str());
imp.reset_source_selection
.set_factory(Some(&create_dropdown_label_factory()));
set_combo_row_ellipsis(imp.reset_source_selection.get());
let volume = stream.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
imp.stream.replace(stream);
imp.reset_volume_slider.connect_change_value(
clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| {
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
let mut stream = imp.stream.try_borrow();
while stream.is_err() {
stream = imp.stream.try_borrow();
}
let stream = stream.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());
}
set_outputstream_volume(value, index, channels);
Propagation::Proceed
}),
);
{
let list = box_imp.reset_model_list.read().unwrap();
imp.reset_source_selection.set_model(Some(&*list));
let source_list = box_imp.reset_source_list.read().unwrap();
let name = source_list.get(&index);
let index = box_imp.reset_model_index.read().unwrap();
let model_list = box_imp.reset_model_list.read().unwrap();
if let Some(name) = name {
for entry in 0..*index {
if model_list.string(entry) == Some(name.2.clone().into()) {
imp.reset_source_selection.set_selected(entry);
break;
}
}
} else {
let mut name = box_imp.reset_default_source.try_borrow();
while name.is_err() {
name = box_imp.reset_default_source.try_borrow();
}
let name = name.unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(name.alias.clone().into()) {
imp.reset_source_selection.set_selected(entry);
break;
}
}
}
}
imp.reset_source_selection.connect_selected_notify(
clone!(@weak imp, @weak box_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 source = box_imp.reset_source_map.write().unwrap();
let source = source.get(&selected);
if source.is_none() {
return;
}
let mut stream = imp.stream.try_borrow();
while stream.is_err() {
stream = imp.stream.try_borrow();
}
let stream = stream.unwrap();
let source = source.unwrap().0;
set_source_of_output_stream(stream.index, source);
}),
);
imp.reset_source_mute
.connect_clicked(clone!(@weak imp => move |_| {
let stream = imp.stream.clone();
let mut stream = stream.try_borrow_mut();
while stream.is_err() {
stream = imp.stream.try_borrow_mut();
}
let mut stream = stream.unwrap();
stream.muted = !stream.muted;
let muted = stream.muted;
let index = stream.index;
if muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
toggle_output_stream_mute(index, muted);
}));
}
obj
}
}
fn set_outputstream_volume(value: f64, index: u32, channels: u16) -> 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,
"SetOutputStreamVolume",
(index, channels, value as u32),
);
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
fn toggle_output_stream_mute(index: u32, muted: bool) -> 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, "SetOutputStreamMute", (index, muted));
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
fn set_source_of_output_stream(stream: u32, source: u32) -> 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<(bool,), Error> =
proxy.method_call(AUDIO, "SetSourceOfOutputStream", (stream, source));
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
// TODO propagate error from dbus

View file

@ -1,48 +0,0 @@
use adw::subclass::prelude::PreferencesGroupImpl;
use adw::{ComboRow, PreferencesGroup};
use re_set_lib::audio::audio_structures::OutputStream;
use std::cell::RefCell;
use std::sync::Arc;
use std::time::SystemTime;
use crate::components::input::output_stream_entry;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Label, Scale};
#[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 {}

View file

@ -1,655 +0,0 @@
use adw::prelude::PreferencesRowExt;
use re_set_lib::audio::audio_structures::{Card, OutputStream, Source};
use re_set_lib::signals::{
OutputStreamAdded, OutputStreamChanged, OutputStreamRemoved, SourceAdded, SourceChanged,
SourceRemoved,
};
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use adw::glib;
use adw::glib::Object;
use adw::prelude::{
BoxExt, ButtonExt, CheckButtonExt, ComboRowExt, ListBoxRowExt, PreferencesGroupExt, RangeExt,
};
use dbus::blocking::Connection;
use dbus::message::SignalArgs;
use dbus::{Error, Path};
use glib::subclass::prelude::ObjectSubclassIsExt;
use glib::{clone, Cast, Propagation, Variant};
use gtk::prelude::ActionableExt;
use gtk::{gio, StringObject};
use crate::components::base::card_entry::CardEntry;
use crate::components::base::list_entry::ListEntry;
use crate::components::input::source_box_impl;
use crate::components::input::source_entry::set_source_volume;
use crate::components::utils::{
create_dropdown_label_factory, set_combo_row_ellipsis, AUDIO, BASE, DBUS_PATH,
};
use super::output_stream_entry::OutputStreamEntry;
use super::source_entry::{set_default_source, toggle_source_mute, 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 SourceBox {
pub fn new() -> Self {
let obj: Self = Object::builder().build();
{
let imp = obj.imp();
let mut model_index = imp.reset_model_index.write().unwrap();
*model_index = 0;
}
obj
}
pub fn setup_callbacks(&self) {
let self_imp = self.imp();
self_imp.reset_source_row.set_activatable(true);
self_imp
.reset_source_row
.set_action_name(Some("navigation.push"));
self_imp
.reset_source_row
.set_action_target_value(Some(&Variant::from("sources")));
self_imp.reset_cards_row.set_activatable(true);
self_imp
.reset_cards_row
.set_action_name(Some("navigation.push"));
self_imp
.reset_cards_row
.set_action_target_value(Some(&Variant::from("profileConfiguration")));
self_imp.reset_output_stream_button.set_activatable(true);
self_imp
.reset_output_stream_button
.set_action_name(Some("navigation.pop"));
self_imp.reset_input_cards_back_button.set_activatable(true);
self_imp
.reset_input_cards_back_button
.set_action_name(Some("navigation.pop"));
self_imp
.reset_source_dropdown
.set_factory(Some(&create_dropdown_label_factory()));
set_combo_row_ellipsis(self_imp.reset_source_dropdown.get());
}
}
impl Default for SourceBox {
fn default() -> Self {
Self::new()
}
}
pub fn populate_sources(input_box: Arc<SourceBox>) {
gio::spawn_blocking(move || {
let sources = get_sources();
{
let input_box_imp = input_box.imp();
let list = input_box_imp.reset_model_list.write().unwrap();
let mut map = input_box_imp.reset_source_map.write().unwrap();
let mut model_index = input_box_imp.reset_model_index.write().unwrap();
input_box_imp
.reset_default_source
.replace(get_default_source());
for source in sources.iter() {
list.append(&source.alias);
map.insert(source.alias.clone(), (source.index, source.name.clone()));
*model_index += 1;
}
}
populate_outputstreams(input_box.clone());
populate_cards(input_box.clone());
glib::spawn_future(async move {
glib::idle_add_once(move || {
let input_box_ref_slider = input_box.clone();
let input_box_ref_toggle = input_box.clone();
let input_box_ref_mute = input_box.clone();
let input_box_ref = input_box.clone();
{
let input_box_imp = input_box_ref.imp();
let default_sink = input_box_imp.reset_default_source.clone();
let source = default_sink.borrow();
if source.muted {
input_box_imp
.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
input_box_imp
.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
let volume = source.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
input_box_imp.reset_volume_percentage.set_text(&percentage);
input_box_imp.reset_volume_slider.set_value(*volume as f64);
let mut list = input_box_imp.reset_source_list.write().unwrap();
for source in sources {
let index = source.index;
let alias = source.alias.clone();
let mut is_default = false;
if input_box_imp.reset_default_source.borrow().name == source.name {
is_default = true;
}
let source_entry = Arc::new(SourceEntry::new(
is_default,
input_box_imp.reset_default_check_button.clone(),
source,
input_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));
input_box_imp.reset_sources.append(&*entry);
}
let list = input_box_imp.reset_model_list.read().unwrap();
input_box_imp.reset_source_dropdown.set_model(Some(&*list));
let name = input_box_imp.reset_default_source.borrow();
let index = input_box_imp.reset_model_index.read().unwrap();
let model_list = input_box_imp.reset_model_list.read().unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(name.alias.clone().into()) {
input_box_imp.reset_source_dropdown.set_selected(entry);
break;
}
}
input_box_imp.reset_source_dropdown.connect_selected_notify(
clone!(@weak input_box_imp => move |dropdown| {
let input_box = input_box_ref_toggle.clone();
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 source = input_box_imp.reset_source_map.read().unwrap();
let source = source.get(&selected);
if source.is_none() {
return;
}
let source = Arc::new(source.unwrap().1.clone());
gio::spawn_blocking(move || {
let result = set_default_source(source);
if result.is_none(){
return;
}
refresh_default_source(result.unwrap(), input_box.clone(), false);
});
}),
);
}
input_box_ref
.imp()
.reset_volume_slider
.connect_change_value(move |_, _, value| {
let imp = input_box_ref_slider.imp();
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
let source = imp.reset_default_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());
}
set_source_volume(value, index, channels);
Propagation::Proceed
});
input_box_ref
.imp()
.reset_source_mute
.connect_clicked(move |_| {
let imp = input_box_ref_mute.imp();
let mut source = imp.reset_default_source.borrow_mut();
source.muted = !source.muted;
if source.muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
toggle_source_mute(source.index, source.muted);
});
});
});
});
}
pub fn refresh_default_source(new_source: Source, input_box: Arc<SourceBox>, entry: bool) {
let volume = *new_source.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 = input_box.imp();
if !entry {
let list = imp.reset_source_list.read().unwrap();
let entry = list.get(&new_source.index);
if entry.is_none() {
return;
}
let entry_imp = entry.unwrap().1.imp();
entry_imp.reset_selected_source.set_active(true);
} else {
let model_list = imp.reset_model_list.read().unwrap();
for entry in 0..*imp.reset_model_index.read().unwrap() {
if model_list.string(entry) == Some(new_source.alias.clone().into()) {
imp.reset_source_dropdown.set_selected(entry);
break;
}
}
}
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(volume as f64);
if new_source.muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
imp.reset_default_source.replace(new_source);
});
});
}
pub fn populate_outputstreams(input_box: Arc<SourceBox>) {
let input_box_ref = input_box.clone();
gio::spawn_blocking(move || {
let streams = get_output_streams();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let input_box_imp = input_box_ref.imp();
let mut list = input_box_imp.reset_output_stream_list.write().unwrap();
for stream in streams {
let index = stream.index;
let input_stream = Arc::new(OutputStreamEntry::new(input_box.clone(), stream));
let input_stream_clone = input_stream.clone();
let entry = Arc::new(ListEntry::new(&*input_stream));
entry.set_activatable(false);
list.insert(index, (entry.clone(), input_stream_clone));
input_box_imp.reset_output_streams.append(&*entry);
}
});
});
});
}
pub fn populate_cards(input_box: Arc<SourceBox>) {
gio::spawn_blocking(move || {
let input_box_ref = input_box.clone();
let cards = get_cards();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = input_box_ref.imp();
for card in cards {
imp.reset_cards.add(&CardEntry::new(card));
}
});
});
});
}
fn get_output_streams() -> Vec<OutputStream> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<OutputStream>,), Error> =
proxy.method_call(AUDIO, "ListOutputStreams", ());
if res.is_err() {
return Vec::new();
}
res.unwrap().0
}
fn get_sources() -> Vec<Source> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<Source>,), Error> = proxy.method_call(AUDIO, "ListSources", ());
if res.is_err() {
return Vec::new();
}
res.unwrap().0
}
fn get_cards() -> Vec<Card> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<Card>,), Error> = proxy.method_call(AUDIO, "ListCards", ());
if res.is_err() {
return Vec::new();
}
res.unwrap().0
}
fn get_default_source_name() -> String {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(String,), Error> = proxy.method_call(AUDIO, "GetDefaultSourceName", ());
if res.is_err() {
return String::from("");
}
res.unwrap().0
}
fn get_default_source() -> Source {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Source,), Error> = proxy.method_call(AUDIO, "GetDefaultSource", ());
if res.is_err() {
return Source::default();
}
res.unwrap().0
}
pub fn start_input_box_listener(conn: Connection, source_box: Arc<SourceBox>) -> Connection {
let source_added =
SourceAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let source_removed =
SourceRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let source_changed =
SourceChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let output_stream_added =
OutputStreamAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let output_stream_removed =
OutputStreamRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let output_stream_changed =
OutputStreamChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let source_added_box = source_box.clone();
let source_removed_box = source_box.clone();
let source_changed_box = source_box.clone();
let output_stream_added_box = source_box.clone();
let output_stream_removed_box = source_box.clone();
let output_stream_changed_box = source_box.clone();
let res = conn.add_match(source_added, move |ir: SourceAdded, _, _| {
let source_box = source_added_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let input_box = source_box.clone();
let input_box_imp = input_box.imp();
let source_index = ir.source.index;
let alias = ir.source.alias.clone();
let name = ir.source.name.clone();
let mut is_default = false;
if input_box_imp.reset_default_source.borrow().name == ir.source.name {
is_default = true;
}
let source_entry = Arc::new(SourceEntry::new(
is_default,
input_box_imp.reset_default_check_button.clone(),
ir.source,
input_box.clone(),
));
let source_clone = source_entry.clone();
let entry = Arc::new(ListEntry::new(&*source_entry));
entry.set_activatable(false);
let mut list = input_box_imp.reset_source_list.write().unwrap();
list.insert(source_index, (entry.clone(), source_clone, alias.clone()));
input_box_imp.reset_sources.append(&*entry);
let mut map = input_box_imp.reset_source_map.write().unwrap();
let mut index = input_box_imp.reset_model_index.write().unwrap();
let model_list = input_box_imp.reset_model_list.write().unwrap();
if model_list.string(*index - 1) == Some("Monitor of Dummy Output".into()) {
model_list.append(&alias);
model_list.remove(*index - 1);
map.insert(alias, (source_index, name));
input_box_imp.reset_source_dropdown.set_selected(0);
} else {
model_list.append(&alias);
map.insert(alias.clone(), (source_index, name));
if alias == "Monitor of Dummy Output" {
input_box_imp.reset_source_dropdown.set_selected(0);
}
*index += 1;
}
});
});
true
});
if res.is_err() {
println!("fail on source add event");
return conn;
}
let res = conn.add_match(source_removed, move |ir: SourceRemoved, _, _| {
let source_box = source_removed_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let input_box = source_box.clone();
let input_box_imp = input_box.imp();
let entry: Option<(Arc<ListEntry>, Arc<SourceEntry>, String)>;
{
let mut list = input_box_imp.reset_source_list.write().unwrap();
entry = list.remove(&ir.index);
if entry.is_none() {
return;
}
}
input_box_imp
.reset_sources
.remove(&*entry.clone().unwrap().0);
let mut map = input_box_imp.reset_source_map.write().unwrap();
let alias = entry.unwrap().2;
map.remove(&alias);
let mut index = input_box_imp.reset_model_index.write().unwrap();
let model_list = input_box_imp.reset_model_list.write().unwrap();
if *index == 1 {
model_list.append("Monitor of Dummy Output");
}
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
});
if res.is_err() {
println!("fail on source remove event");
return conn;
}
let res = conn.add_match(source_changed, move |ir: SourceChanged, _, _| {
let source_box = source_changed_box.clone();
let default_source = get_default_source_name();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let input_box = source_box.clone();
let input_box_imp = input_box.imp();
let is_default = ir.source.name == default_source;
let volume = ir.source.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
let list = input_box_imp.reset_source_list.read().unwrap();
let entry = list.get(&ir.source.index);
if entry.is_none() {
return;
}
let imp = entry.unwrap().1.imp();
if is_default {
input_box_imp.reset_volume_percentage.set_text(&percentage);
input_box_imp.reset_volume_slider.set_value(*volume as f64);
input_box_imp
.reset_default_source
.replace(ir.source.clone());
if ir.source.muted {
input_box_imp
.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
input_box_imp
.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
imp.reset_selected_source.set_active(true);
} else {
imp.reset_selected_source.set_active(false);
}
imp.reset_source_name
.set_title(ir.source.alias.clone().as_str());
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
if ir.source.muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
});
});
true
});
if res.is_err() {
println!("fail on source change event");
return conn;
}
let res = conn.add_match(output_stream_added, move |ir: OutputStreamAdded, _, _| {
let source_box = output_stream_added_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let input_box = source_box.clone();
let input_box_imp = input_box.imp();
let mut list = input_box_imp.reset_output_stream_list.write().unwrap();
let index = ir.stream.index;
let output_stream = Arc::new(OutputStreamEntry::new(input_box.clone(), ir.stream));
let entry = Arc::new(ListEntry::new(&*output_stream));
entry.set_activatable(false);
list.insert(index, (entry.clone(), output_stream.clone()));
input_box_imp.reset_output_streams.append(&*entry);
});
});
true
});
if res.is_err() {
println!("fail on output stream add event");
return conn;
}
let res = conn.add_match(
output_stream_changed,
move |ir: OutputStreamChanged, _, _| {
let imp = output_stream_changed_box.imp();
let alias: String;
{
let source_list = imp.reset_source_list.read().unwrap();
if let Some(alias_opt) = source_list.get(&ir.stream.source_index) {
alias = alias_opt.2.clone();
} else {
alias = String::from("");
}
}
let source_box = output_stream_changed_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let input_box = source_box.clone();
let input_box_imp = input_box.imp();
let entry: Arc<OutputStreamEntry>;
{
let list = input_box_imp.reset_output_stream_list.read().unwrap();
let entry_opt = list.get(&ir.stream.index);
if entry_opt.is_none() {
return;
}
entry = entry_opt.unwrap().1.clone();
}
let imp = entry.imp();
if ir.stream.muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
let name = ir.stream.application_name.clone() + ": " + ir.stream.name.as_str();
imp.reset_source_selection.set_title(name.as_str());
let volume = ir.stream.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
let index = input_box_imp.reset_model_index.read().unwrap();
let model_list = input_box_imp.reset_model_list.read().unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(alias.clone().into()) {
imp.reset_source_selection.set_selected(entry);
break;
}
}
});
});
true
},
);
if res.is_err() {
println!("fail on output stream change event");
return conn;
}
let res = conn.add_match(
output_stream_removed,
move |ir: OutputStreamRemoved, _, _| {
let source_box = output_stream_removed_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let input_box = source_box.clone();
let input_box_imp = input_box.imp();
let mut list = input_box_imp.reset_output_stream_list.write().unwrap();
let entry = list.remove(&ir.index);
if entry.is_none() {
return;
}
input_box_imp
.reset_output_streams
.remove(&*entry.unwrap().0);
});
});
true
},
);
if res.is_err() {
println!("fail on output stream remove event");
return conn;
}
conn
}

View file

@ -1,163 +0,0 @@
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use adw::glib;
use adw::glib::Object;
use adw::prelude::{ButtonExt, CheckButtonExt, PreferencesRowExt, RangeExt};
use dbus::blocking::Connection;
use dbus::Error;
use glib::subclass::types::ObjectSubclassIsExt;
use glib::{clone, Propagation};
use gtk::{gio, CheckButton};
use re_set_lib::audio::audio_structures::Source;
use crate::components::utils::set_action_row_ellipsis;
use crate::components::utils::{BASE, DBUS_PATH, AUDIO};
use super::source_box::{refresh_default_source, 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 SourceEntry {
pub fn new(
is_default: bool,
check_group: Arc<CheckButton>,
source: Source,
input_box: Arc<SourceBox>,
) -> Self {
let obj: Self = Object::builder().build();
// TODO use event callback for progress bar -> this is the "im speaking" indicator
{
let imp = obj.imp();
imp.reset_source_name
.set_title(source.alias.clone().as_str());
let name = Arc::new(source.name.clone());
let volume = source.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
imp.source.replace(source);
imp.reset_volume_slider.connect_change_value(
clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| {
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
let source = imp.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());
}
set_source_volume(value, index, channels);
Propagation::Proceed
}),
);
imp.reset_selected_source.set_group(Some(&*check_group));
if is_default {
imp.reset_selected_source.set_active(true);
} else {
imp.reset_selected_source.set_active(false);
}
imp.reset_selected_source.connect_toggled(move |button| {
let input_box = input_box.clone();
if button.is_active() {
let name = name.clone();
gio::spawn_blocking(move || {
let result = set_default_source(name);
if result.is_none() {
return;
}
refresh_default_source(result.unwrap(), input_box, true);
});
}
});
imp.reset_source_mute
.connect_clicked(clone!(@weak imp => move |_| {
let mut source = imp.source.borrow_mut();
source.muted = !source.muted;
if source.muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
toggle_source_mute(source.index, source.muted);
}));
set_action_row_ellipsis(imp.reset_source_name.get());
}
obj
}
}
pub fn set_source_volume(value: f64, index: u32, channels: u16) -> 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,
"SetSourceVolume",
(index, channels, value as u32),
);
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
pub fn toggle_source_mute(index: u32, muted: bool) -> 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, "SetSourceMute", (index, muted));
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
pub fn set_default_source(name: Arc<String>) -> Option<Source> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
BASE,
DBUS_PATH,
Duration::from_millis(1000),
);
let res: Result<(Source,), Error> = proxy.method_call(
AUDIO,
"SetDefaultSource",
(name.as_str(),),
);
if res.is_err() {
return None;
}
Some(res.unwrap().0)
}

View file

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

View file

@ -1,205 +0,0 @@
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use crate::components::utils::{
create_dropdown_label_factory, set_combo_row_ellipsis, AUDIO, BASE, DBUS_PATH,
};
use adw::glib;
use adw::glib::Object;
use adw::prelude::{ButtonExt, ComboRowExt, PreferencesRowExt, RangeExt};
use dbus::blocking::Connection;
use dbus::Error;
use glib::subclass::types::ObjectSubclassIsExt;
use glib::{clone, Cast, Propagation};
use gtk::{gio, StringObject};
use re_set_lib::audio::audio_structures::InputStream;
use super::input_stream_entry_impl;
use super::sink_box::SinkBox;
glib::wrapper! {
pub struct InputStreamEntry(ObjectSubclass<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 InputStreamEntry {
pub fn new(sink_box: Arc<SinkBox>, stream: InputStream) -> Self {
let obj: Self = Object::builder().build();
// TODO use event callback for progress bar -> this is the "im speaking" indicator
{
let index = stream.sink_index;
let box_imp = sink_box.imp();
let imp = obj.imp();
if stream.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
let name = stream.application_name.clone() + ": " + stream.name.as_str();
imp.reset_sink_selection.set_title(name.as_str());
imp.reset_sink_selection
.set_factory(Some(&create_dropdown_label_factory()));
set_combo_row_ellipsis(imp.reset_sink_selection.get());
let volume = stream.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
imp.stream.replace(stream);
{
let sink = box_imp.reset_default_sink.borrow();
imp.associated_sink.replace((sink.index, sink.name.clone()));
}
imp.reset_volume_slider.connect_change_value(
clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| {
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
let mut stream = imp.stream.try_borrow();
while stream.is_err() {
stream = imp.stream.try_borrow();
}
let stream = stream.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());
}
set_inputstream_volume(value, index, channels);
Propagation::Proceed
}),
);
{
let list = box_imp.reset_model_list.read().unwrap();
imp.reset_sink_selection.set_model(Some(&*list));
let sink_list = box_imp.reset_sink_list.read().unwrap();
let name = sink_list.get(&index);
let index = box_imp.reset_model_index.read().unwrap();
let model_list = box_imp.reset_model_list.read().unwrap();
if let Some(name) = name {
for entry in 0..*index {
if model_list.string(entry) == Some(name.2.clone().into()) {
imp.reset_sink_selection.set_selected(entry);
break;
}
}
} else {
let mut name = box_imp.reset_default_sink.try_borrow();
while name.is_err() {
name = box_imp.reset_default_sink.try_borrow();
}
let name = name.unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(name.alias.clone().into()) {
imp.reset_sink_selection.set_selected(entry);
break;
}
}
}
}
imp.reset_sink_selection.connect_selected_notify(
clone!(@weak imp, @weak box_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 sink = box_imp.reset_sink_map.read().unwrap();
// if sink.is_err() {
// return;
// }
// let sink = sink.unwrap();
let sink = sink.get(&selected);
if sink.is_none() {
return;
}
let mut stream = imp.stream.try_borrow();
while stream.is_err() {
stream = imp.stream.try_borrow();
}
let stream = stream.unwrap();
let sink = sink.unwrap().0;
set_sink_of_input_stream(stream.index, sink);
}),
);
imp.reset_sink_mute
.connect_clicked(clone!(@weak imp => move |_| {
let stream = imp.stream.clone();
let mut stream = stream.try_borrow_mut();
while stream.is_err() {
stream = imp.stream.try_borrow_mut();
}
let mut stream = stream.unwrap();
stream.muted = !stream.muted;
let muted = stream.muted;
let index = stream.index;
if muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
toggle_input_stream_mute(index, muted);
}));
}
obj
}
}
fn set_inputstream_volume(value: f64, index: u32, channels: u16) -> 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,
"SetInputStreamVolume",
(index, channels, value as u32),
);
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
fn toggle_input_stream_mute(index: u32, muted: bool) -> 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, "SetInputStreamMute", (index, muted));
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
fn set_sink_of_input_stream(stream: u32, sink: u32) -> 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, "SetSinkOfInputStream", (stream, sink));
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
// TODO propagate error from dbus

View file

@ -1,49 +0,0 @@
use adw::subclass::prelude::PreferencesGroupImpl;
use adw::{ComboRow, PreferencesGroup};
use re_set_lib::audio::audio_structures::InputStream;
use std::cell::RefCell;
use std::sync::Arc;
use std::time::SystemTime;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Label, Scale};
use super::input_stream_entry;
#[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 {}

View file

@ -1,655 +0,0 @@
use adw::prelude::PreferencesGroupExt;
use adw::prelude::PreferencesRowExt;
use re_set_lib::audio::audio_structures::Card;
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 std::time::{Duration, SystemTime};
use adw::glib::Object;
use adw::prelude::{BoxExt, ButtonExt, CheckButtonExt, ComboRowExt, RangeExt};
use adw::{glib, prelude::ListBoxRowExt};
use dbus::blocking::Connection;
use dbus::message::SignalArgs;
use dbus::{Error, Path};
use glib::subclass::prelude::ObjectSubclassIsExt;
use glib::{clone, Cast, Propagation, Variant};
use gtk::prelude::ActionableExt;
use gtk::{gio, StringObject};
use crate::components::base::card_entry::CardEntry;
use crate::components::base::list_entry::ListEntry;
use crate::components::output::sink_entry::set_sink_volume;
use crate::components::utils::AUDIO;
use crate::components::utils::BASE;
use crate::components::utils::DBUS_PATH;
use crate::components::utils::{create_dropdown_label_factory, set_combo_row_ellipsis};
use super::input_stream_entry::InputStreamEntry;
use super::sink_box_impl;
use super::sink_entry::{set_default_sink, toggle_sink_mute, 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 SinkBox {
pub fn new() -> Self {
let obj: Self = Object::builder().build();
{
let imp = obj.imp();
let mut model_index = imp.reset_model_index.write().unwrap();
*model_index = 0;
}
obj
}
pub fn setup_callbacks(&self) {
let self_imp = self.imp();
self_imp.reset_sinks_row.set_activatable(true);
self_imp
.reset_sinks_row
.set_action_name(Some("navigation.push"));
self_imp
.reset_sinks_row
.set_action_target_value(Some(&Variant::from("outputDevices")));
self_imp.reset_cards_row.set_activatable(true);
self_imp
.reset_cards_row
.set_action_name(Some("navigation.push"));
self_imp
.reset_cards_row
.set_action_target_value(Some(&Variant::from("profileConfiguration")));
self_imp.reset_input_stream_button.set_activatable(true);
self_imp
.reset_input_stream_button
.set_action_name(Some("navigation.pop"));
self_imp.reset_input_cards_back_button.set_activatable(true);
self_imp
.reset_input_cards_back_button
.set_action_name(Some("navigation.pop"));
self_imp
.reset_sink_dropdown
.set_factory(Some(&create_dropdown_label_factory()));
set_combo_row_ellipsis(self_imp.reset_sink_dropdown.get());
}
}
impl Default for SinkBox {
fn default() -> Self {
Self::new()
}
}
pub fn populate_sinks(output_box: Arc<SinkBox>) {
gio::spawn_blocking(move || {
let sinks = get_sinks();
{
let output_box_imp = output_box.imp();
let list = output_box_imp.reset_model_list.write().unwrap();
let mut map = output_box_imp.reset_sink_map.write().unwrap();
let mut model_index = output_box_imp.reset_model_index.write().unwrap();
output_box_imp
.reset_default_sink
.replace(get_default_sink());
for sink in sinks.iter() {
list.append(&sink.alias);
map.insert(sink.alias.clone(), (sink.index, sink.name.clone()));
*model_index += 1;
}
}
populate_inputstreams(output_box.clone());
populate_cards(output_box.clone());
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box_ref_select = output_box.clone();
let output_box_ref_slider = output_box.clone();
let output_box_ref_mute = output_box.clone();
let output_box_ref = output_box.clone();
{
let output_box_imp = output_box_ref.imp();
let default_sink = output_box_imp.reset_default_sink.clone();
let sink = default_sink.borrow();
if sink.muted {
output_box_imp
.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
output_box_imp
.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
let volume = sink.volume.first().unwrap_or(&0);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
output_box_imp.reset_volume_percentage.set_text(&percentage);
output_box_imp.reset_volume_slider.set_value(*volume as f64);
let mut list = output_box_imp.reset_sink_list.write().unwrap();
for sink in sinks {
let index = sink.index;
let alias = sink.alias.clone();
let mut is_default = false;
if output_box_imp.reset_default_sink.borrow().name == sink.name {
is_default = true;
}
let sink_entry = Arc::new(SinkEntry::new(
is_default,
output_box_imp.reset_default_check_button.clone(),
sink,
output_box.clone(),
));
let sink_clone = sink_entry.clone();
let entry = Arc::new(ListEntry::new(&*sink_entry));
entry.set_activatable(false);
list.insert(index, (entry.clone(), sink_clone, alias));
output_box_imp.reset_sinks.append(&*entry);
}
let list = output_box_imp.reset_model_list.read().unwrap();
output_box_imp.reset_sink_dropdown.set_model(Some(&*list));
let name = output_box_imp.reset_default_sink.borrow();
let index = output_box_imp.reset_model_index.read().unwrap();
let model_list = output_box_imp.reset_model_list.read().unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(name.alias.clone().into()) {
output_box_imp.reset_sink_dropdown.set_selected(entry);
break;
}
}
output_box_imp.reset_sink_dropdown.connect_selected_notify(
clone!(@weak output_box_imp => move |dropdown| {
let output_box_ref = output_box_ref_select.clone();
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 = output_box_imp.reset_sink_map.read().unwrap();
let sink = sink.get(&selected);
if sink.is_none() {
return;
}
let new_sink_name = Arc::new(sink.unwrap().1.clone());
gio::spawn_blocking(move || {
let result = set_default_sink(new_sink_name);
if result.is_none() {
return;
}
let new_sink = result.unwrap();
refresh_default_sink(new_sink, output_box_ref, false);
});
}),
);
}
output_box_ref
.imp()
.reset_volume_slider
.connect_change_value(move |_, _, value| {
let imp = output_box_ref_slider.imp();
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
let sink = imp.reset_default_sink.borrow();
let index = sink.index;
let channels = sink.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());
}
set_sink_volume(value, index, channels);
Propagation::Proceed
});
output_box_ref
.imp()
.reset_sink_mute
.connect_clicked(move |_| {
let imp = output_box_ref_mute.imp();
let mut stream = imp.reset_default_sink.borrow_mut();
stream.muted = !stream.muted;
if stream.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
toggle_sink_mute(stream.index, stream.muted);
});
});
});
});
}
pub fn refresh_default_sink(new_sink: Sink, output_box: Arc<SinkBox>, entry: bool) {
let volume = *new_sink.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 = output_box.imp();
if !entry {
let list = imp.reset_sink_list.read().unwrap();
let entry = list.get(&new_sink.index);
if entry.is_none() {
return;
}
let entry_imp = entry.unwrap().1.imp();
entry_imp.reset_selected_sink.set_active(true);
} else {
let index = imp.reset_model_index.read().unwrap();
let model_list = imp.reset_model_list.read().unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(new_sink.alias.clone().into()) {
imp.reset_sink_dropdown.set_selected(entry);
break;
}
}
}
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(volume as f64);
if new_sink.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
imp.reset_default_sink.replace(new_sink);
});
});
}
pub fn populate_inputstreams(output_box: Arc<SinkBox>) {
let output_box_ref = output_box.clone();
gio::spawn_blocking(move || {
let streams = get_input_streams();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box_imp = output_box_ref.imp();
let mut list = output_box_imp.reset_input_stream_list.write().unwrap();
for stream in streams {
let index = stream.index;
let input_stream = Arc::new(InputStreamEntry::new(output_box.clone(), stream));
let entry = Arc::new(ListEntry::new(&*input_stream));
entry.set_activatable(false);
list.insert(index, (entry.clone(), input_stream.clone()));
output_box_imp.reset_input_streams.append(&*entry);
}
});
});
});
}
pub fn populate_cards(output_box: Arc<SinkBox>) {
gio::spawn_blocking(move || {
let output_box_ref = output_box.clone();
let cards = get_cards();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = output_box_ref.imp();
for card in cards {
imp.reset_cards.add(&CardEntry::new(card));
}
});
});
});
}
fn get_input_streams() -> Vec<InputStream> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<InputStream>,), Error> = proxy.method_call(AUDIO, "ListInputStreams", ());
if res.is_err() {
return Vec::new();
}
res.unwrap().0
}
fn get_sinks() -> Vec<Sink> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<Sink>,), Error> = proxy.method_call(AUDIO, "ListSinks", ());
if res.is_err() {
return Vec::new();
}
res.unwrap().0
}
fn get_cards() -> Vec<Card> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Vec<Card>,), Error> = proxy.method_call(AUDIO, "ListCards", ());
if res.is_err() {
return Vec::new();
}
res.unwrap().0
}
fn get_default_sink_name() -> String {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(String,), Error> = proxy.method_call(AUDIO, "GetDefaultSinkName", ());
if res.is_err() {
return String::from("");
}
res.unwrap().0
}
fn get_default_sink() -> Sink {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let res: Result<(Sink,), Error> = proxy.method_call(AUDIO, "GetDefaultSink", ());
if res.is_err() {
return Sink::default();
}
res.unwrap().0
}
pub fn start_output_box_listener(conn: Connection, sink_box: Arc<SinkBox>) -> Connection {
let sink_added =
SinkAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let sink_removed =
SinkRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let sink_changed =
SinkChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))).static_clone();
let input_stream_added =
InputStreamAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let input_stream_removed =
InputStreamRemoved::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let input_stream_changed =
InputStreamChanged::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let sink_added_box = sink_box.clone();
let sink_removed_box = sink_box.clone();
let sink_changed_box = sink_box.clone();
let input_stream_added_box = sink_box.clone();
let input_stream_removed_box = sink_box.clone();
let input_stream_changed_box = sink_box.clone();
let res = conn.add_match(sink_added, move |ir: SinkAdded, _, _| {
let sink_box = sink_added_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = sink_box.clone();
let output_box_imp = output_box.imp();
let sink_index = ir.sink.index;
let alias = ir.sink.alias.clone();
let name = ir.sink.name.clone();
let mut is_default = false;
if output_box_imp.reset_default_sink.borrow().name == ir.sink.name {
is_default = true;
}
let sink_entry = Arc::new(SinkEntry::new(
is_default,
output_box_imp.reset_default_check_button.clone(),
ir.sink,
output_box.clone(),
));
let sink_clone = sink_entry.clone();
let entry = Arc::new(ListEntry::new(&*sink_entry));
entry.set_activatable(false);
let mut list = output_box_imp.reset_sink_list.write().unwrap();
list.insert(sink_index, (entry.clone(), sink_clone, alias.clone()));
output_box_imp.reset_sinks.append(&*entry);
let mut map = output_box_imp.reset_sink_map.write().unwrap();
let mut index = output_box_imp.reset_model_index.write().unwrap();
let model_list = output_box_imp.reset_model_list.write().unwrap();
if model_list.string(*index - 1) == Some("Dummy Output".into()) {
model_list.append(&alias);
model_list.remove(*index - 1);
map.insert(alias, (sink_index, name));
output_box_imp.reset_sink_dropdown.set_selected(0);
} else {
model_list.append(&alias);
map.insert(alias.clone(), (sink_index, name));
if alias == "Dummy Output" {
output_box_imp.reset_sink_dropdown.set_selected(0);
}
*index += 1;
}
});
});
true
});
if res.is_err() {
println!("fail on sink add event");
return conn;
}
let res = conn.add_match(sink_removed, move |ir: SinkRemoved, _, _| {
let sink_box = sink_removed_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = sink_box.clone();
let output_box_imp = output_box.imp();
let entry: Option<(Arc<ListEntry>, Arc<SinkEntry>, String)>;
{
let mut list = output_box_imp.reset_sink_list.write().unwrap();
entry = list.remove(&ir.index);
if entry.is_none() {
return;
}
}
output_box_imp
.reset_sinks
.remove(&*entry.clone().unwrap().0);
let alias = entry.unwrap().2;
let mut index = output_box_imp.reset_model_index.write().unwrap();
let model_list = output_box_imp.reset_model_list.write().unwrap();
// add dummy entry when no other devices are available
if *index == 1 {
model_list.append("Dummy Output");
}
let mut map = output_box_imp.reset_sink_map.write().unwrap();
map.remove(&alias);
for entry in 0..*index {
if model_list.string(entry) == Some(alias.clone().into()) {
model_list.splice(entry, 1, &[]);
break;
}
}
// dummy enforces a minimum of 1
if *index > 1 {
*index -= 1;
}
});
});
true
});
if res.is_err() {
println!("fail on sink remove event");
return conn;
}
let res = conn.add_match(sink_changed, move |ir: SinkChanged, _, _| {
let sink_box = sink_changed_box.clone();
let default_sink = get_default_sink_name();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = sink_box.clone();
let output_box_imp = output_box.imp();
let is_default = ir.sink.name == default_sink;
let volume = ir.sink.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
let list = output_box_imp.reset_sink_list.read().unwrap();
let entry = list.get(&ir.sink.index);
if entry.is_none() {
return;
}
let imp = entry.unwrap().1.imp();
if is_default {
output_box_imp.reset_volume_percentage.set_text(&percentage);
output_box_imp.reset_volume_slider.set_value(*volume as f64);
output_box_imp.reset_default_sink.replace(ir.sink.clone());
if ir.sink.muted {
output_box_imp
.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
output_box_imp
.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
imp.reset_selected_sink.set_active(true);
} else {
imp.reset_selected_sink.set_active(false);
}
imp.reset_sink_name
.set_title(ir.sink.alias.clone().as_str());
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
if ir.sink.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
});
});
true
});
if res.is_err() {
println!("fail on sink change event");
return conn;
}
let res = conn.add_match(input_stream_added, move |ir: InputStreamAdded, _, _| {
let sink_box = input_stream_added_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = sink_box.clone();
let output_box_imp = output_box.imp();
let mut list = output_box_imp.reset_input_stream_list.write().unwrap();
let index = ir.stream.index;
let input_stream = Arc::new(InputStreamEntry::new(output_box.clone(), ir.stream));
let entry = Arc::new(ListEntry::new(&*input_stream));
entry.set_activatable(false);
list.insert(index, (entry.clone(), input_stream.clone()));
output_box_imp.reset_input_streams.append(&*entry);
});
});
true
});
if res.is_err() {
println!("fail on input stream add event");
return conn;
}
let res = conn.add_match(input_stream_changed, move |ir: InputStreamChanged, _, _| {
let imp = input_stream_changed_box.imp();
let alias: String;
{
let sink_list = imp.reset_sink_list.read().unwrap();
if let Some(alias_opt) = sink_list.get(&ir.stream.sink_index) {
alias = alias_opt.2.clone();
} else {
alias = String::from("");
}
}
let sink_box = input_stream_changed_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = sink_box.clone();
let output_box_imp = output_box.imp();
let entry: Arc<InputStreamEntry>;
{
let list = output_box_imp.reset_input_stream_list.read().unwrap();
let entry_opt = list.get(&ir.stream.index);
if entry_opt.is_none() {
return;
}
entry = entry_opt.unwrap().1.clone();
}
let imp = entry.imp();
if ir.stream.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
let name = ir.stream.application_name.clone() + ": " + ir.stream.name.as_str();
imp.reset_sink_selection.set_title(name.as_str());
let volume = ir.stream.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
let index = output_box_imp.reset_model_index.read().unwrap();
let model_list = output_box_imp.reset_model_list.read().unwrap();
for entry in 0..*index {
if model_list.string(entry) == Some(alias.clone().into()) {
imp.reset_sink_selection.set_selected(entry);
break;
}
}
});
});
true
});
if res.is_err() {
println!("fail on input stream change event");
return conn;
}
let res = conn.add_match(input_stream_removed, move |ir: InputStreamRemoved, _, _| {
let sink_box = input_stream_removed_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = sink_box.clone();
let output_box_imp = output_box.imp();
let mut list = output_box_imp.reset_input_stream_list.write().unwrap();
let entry = list.remove(&ir.index);
if entry.is_none() {
return;
}
output_box_imp
.reset_input_streams
.remove(&*entry.unwrap().0);
});
});
true
});
if res.is_err() {
println!("fail on input stream remove event");
return conn;
}
conn
}

View file

@ -1,158 +0,0 @@
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use adw::glib;
use adw::glib::Object;
use adw::prelude::{ButtonExt, CheckButtonExt, PreferencesRowExt, RangeExt};
use dbus::blocking::Connection;
use dbus::Error;
use glib::subclass::types::ObjectSubclassIsExt;
use glib::{clone, Propagation};
use gtk::{gio, CheckButton};
use re_set_lib::audio::audio_structures::Sink;
use crate::components::utils::set_action_row_ellipsis;
use crate::components::utils::{AUDIO, DBUS_PATH, BASE};
use super::sink_box::{refresh_default_sink, 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 SinkEntry {
pub fn new(
is_default: bool,
check_group: Arc<CheckButton>,
stream: Sink,
output_box: Arc<SinkBox>,
) -> Self {
let obj: Self = Object::builder().build();
// TODO use event callback for progress bar -> this is the "im speaking" indicator
{
let imp = obj.imp();
imp.reset_sink_name.set_title(stream.alias.clone().as_str());
let name = Arc::new(stream.name.clone());
let volume = stream.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
imp.stream.replace(stream);
imp.reset_volume_slider.connect_change_value(
clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| {
let fraction = (value / 655.36).round();
let percentage = (fraction).to_string() + "%";
imp.reset_volume_percentage.set_text(&percentage);
let sink = imp.stream.borrow();
let index = sink.index;
let channels = sink.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());
}
set_sink_volume(value, index, channels);
Propagation::Proceed
}),
);
imp.reset_selected_sink.set_group(Some(&*check_group));
if is_default {
imp.reset_selected_sink.set_active(true);
} else {
imp.reset_selected_sink.set_active(false);
}
imp.reset_selected_sink.connect_toggled(move |button| {
let output_box_ref = output_box.clone();
if button.is_active() {
let name = name.clone();
gio::spawn_blocking(move || {
let result = set_default_sink(name);
if result.is_none() {
return;
}
refresh_default_sink(result.unwrap(), output_box_ref, true);
});
}
});
imp.reset_sink_mute
.connect_clicked(clone!(@weak imp => move |_| {
let stream = imp.stream.clone();
let mut stream = stream.borrow_mut();
stream.muted = !stream.muted;
if stream.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
toggle_sink_mute(stream.index, stream.muted);
}));
set_action_row_ellipsis(imp.reset_sink_name.get());
}
obj
}
}
pub fn set_sink_volume(value: f64, index: u32, channels: u16) -> 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,
"SetSinkVolume",
(index, channels, value as u32),
);
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
pub fn toggle_sink_mute(index: u32, muted: bool) -> 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, "SetSinkMute", (index, muted));
// if res.is_err() {
// return false;
// }
// res.unwrap().0
});
true
}
pub fn set_default_sink(name: Arc<String>) -> Option<Sink> {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
BASE,
DBUS_PATH,
Duration::from_millis(1000),
);
let res: Result<(Sink,), Error> =
proxy.method_call(AUDIO, "SetDefaultSink", (name.as_str(),));
if res.is_err() {
return None;
}
Some(res.unwrap().0)
}

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;

View file

@ -1,30 +1,62 @@
use adw::{ActionRow, ComboRow};
use std::cell::Cell;
use std::time::Duration;
use adw::gdk::pango::EllipsizeMode;
use adw::prelude::ListModelExtManual;
use glib::{Cast, Object};
use gtk::{Align, SignalListItemFactory, StringObject};
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.Wireless";
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.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() {
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>() {
@ -59,3 +91,17 @@ pub fn set_action_row_ellipsis(element: ActionRow) {
}
}
}
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

@ -7,6 +7,7 @@ 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;

View file

@ -6,13 +6,13 @@ 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;
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 glib::{clone, PropertySet};
use gtk::prelude::{BoxExt, ListBoxRowExt};
use gtk::{gio, Align, Button, Orientation};
@ -55,7 +55,7 @@ impl SavedWifiEntry {
let entry_ref = entry.clone();
delete_button.connect_clicked(clone!(@weak wifi_box => move |_| {
delete_connection(entry_ref.imp().reset_connection_path.take());
// TODO handle error
// FUTURE TODO: handle error
wifi_box.reset_stored_wifi_list.remove(&*entry_ref);
}));
@ -66,12 +66,7 @@ impl SavedWifiEntry {
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,));
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000));
let _: Result<(), Error> = proxy.method_call(WIRELESS, "DeleteConnection", (path,));
});
}

View file

@ -6,7 +6,7 @@ use std::cell::RefCell;
use dbus::Path;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate};
use gtk::CompositeTemplate;
use super::saved_wifi_entry;

View file

@ -22,13 +22,8 @@ type ResultType =
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,));
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();
}

View file

@ -4,7 +4,6 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::rc::Rc;
use std::str::FromStr;
use adw::glib;
use adw::glib::Object;
use adw::prelude::PreferencesRowExt;
use glib::clone;

View file

@ -2,7 +2,7 @@ use crate::components::wifi::utils::IpProtocol;
use crate::components::wifi::wifi_address_entry;
use adw::{EntryRow, ExpanderRow};
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate};
use gtk::{Button, CompositeTemplate};
use std::cell::{Cell, RefCell};
#[derive(Default, CompositeTemplate)]

View file

@ -4,28 +4,40 @@ 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;
use adw::glib::Object;
use adw::prelude::{ComboRowExt, ListBoxRowExt, PreferencesGroupExt, PreferencesRowExt};
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::{clone, Cast, PropertySet};
use glib::prelude::Cast;
use glib::property::PropertySet;
use glib::{clone, ControlFlow};
use gtk::glib::Variant;
use gtk::prelude::{ActionableExt, WidgetExt};
use gtk::prelude::ActionableExt;
use gtk::{gio, StringList, StringObject};
use re_set_lib::network::network_structures::{AccessPoint, WifiDevice, WifiStrength};
use re_set_lib::signals::{AccessPointAdded, WifiDeviceChanged, WifiDeviceReset};
use re_set_lib::signals::{AccessPointChanged, AccessPointRemoved};
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>)
@ -38,6 +50,14 @@ 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());
@ -50,6 +70,7 @@ impl WifiBox {
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
@ -66,7 +87,7 @@ fn setup_callbacks(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) -> Arc<Wif
if imp.reset_switch_initial.load(Ordering::SeqCst) {
return glib::Propagation::Proceed;
}
set_wifi_enabled(value);
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(&[]);
@ -92,17 +113,16 @@ fn setup_callbacks(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) -> Arc<Wif
pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) {
let wifibox_ref = wifi_box.clone();
let _wifibox_ref_listener = 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();
let devices = get_wifi_devices();
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();
let access_points = get_access_points(wifi_box.clone());
{
let imp = wifibox_ref.imp();
let list = imp.reset_model_list.write().unwrap();
@ -118,7 +138,7 @@ pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) {
}
let wifi_entries = wifi_entries.clone();
let wifi_entries_path = wifi_entries_path.clone();
dbus_start_network_events();
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();
@ -139,24 +159,11 @@ pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) {
}
}
imp.reset_wifi_device.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 device = imp.reset_wifi_devices.read().unwrap();
let device = device.get(&selected);
if device.is_none() {
return;
}
set_wifi_device(device.unwrap().0.path.clone());
}),
);
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;
@ -175,15 +182,34 @@ pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) {
});
}
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();
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 {
// TODO include button for settings
// 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);
@ -194,67 +220,80 @@ pub fn show_stored_connections(wifi_box: Arc<WifiBox>) {
});
}
pub fn dbus_start_network_events() {
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 _: Result<(), Error> = proxy.method_call(WIRELESS, "StartNetworkListener", ());
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() -> Vec<AccessPoint> {
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>) {
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 _: Result<(bool,), Error> = proxy.method_call(WIRELESS, "SetWifiDevice", (path,));
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() -> Vec<WifiDevice> {
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() -> bool {
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() -> Vec<(Path<'static>, Vec<u8>)> {
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) {
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 _: Result<(bool,), Error> = proxy.method_call(WIRELESS, "SetWifiEnabled", (enabled,));
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>) {
@ -288,179 +327,53 @@ pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
WifiDeviceReset::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone();
let res = conn.add_match(access_point_added, move |ir: AccessPointAdded, _, _| {
let wifi_box = added_ref.clone();
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
access_point_added_handler(added_ref.clone(), ir)
});
if res.is_err() {
println!("fail on access point add event");
ERROR!(
"fail on access point add event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(access_point_removed, move |ir: AccessPointRemoved, _, _| {
let wifi_box = removed_ref.clone();
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
access_point_removed_handler(removed_ref.clone(), ir)
});
if res.is_err() {
println!("fail on access point remove event");
ERROR!(
"fail on access point remove event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(access_point_changed, move |ir: AccessPointChanged, _, _| {
let wifi_box = changed_ref.clone();
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);
// 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
access_point_changed_handler(changed_ref.clone(), ir)
});
if res.is_err() {
println!("fail on access point change event");
ERROR!(
"fail on access point change event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(device_changed, move |ir: WifiDeviceChanged, _, _| {
let wifi_box = wifi_changed_ref.clone();
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
wifi_device_changed_handler(wifi_changed_ref.clone(), ir)
});
if res.is_err() {
println!("fail on wifi device change event");
ERROR!(
"fail on wifi device change event",
ErrorLevel::PartialBreakage
);
return;
}
let res = conn.add_match(devices_reset, move |ir: WifiDeviceReset, _, _| {
if ir.devices.is_empty() {
return true;
}
{
let imp = wifi_reset_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(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;
}
}
let wifi_box = wifi_reset_ref.clone();
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
wifi_device_reset_handler(wifi_reset_ref.clone(), ir)
});
if res.is_err() {
println!("fail on wifi device change event");
ERROR!(
"fail on wifi device change event",
ErrorLevel::PartialBreakage
);
return;
}

View file

@ -1,9 +1,10 @@
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::{glib, CompositeTemplate, Switch};
use gtk::{prelude::*, StringList};
use gtk::{CompositeTemplate, Switch};
use re_set_lib::network::network_structures::WifiDevice;
use std::cell::RefCell;
use std::collections::HashMap;
@ -32,6 +33,8 @@ pub struct WifiBox {
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)>>>,

View file

@ -4,13 +4,13 @@ use std::time::Duration;
use crate::components::utils::{BASE, DBUS_PATH, WIRELESS};
use crate::components::wifi::utils::get_connection_settings;
use adw::glib;
use adw::glib::{Object, PropertySet};
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};
@ -48,7 +48,7 @@ impl WifiEntry {
.build(),
);
// TODO handle encryption thing
// FUTURE TODO: handle encryption
let wifi_strength = Image::builder()
.icon_name(match strength {
WifiStrength::Excellent => "network-wireless-signal-excellent-symbolic",
@ -177,7 +177,7 @@ pub fn click_stored_network(entry: Arc<WifiEntry>) {
});
});
});
// TODO crate spinner animation and block UI
// FUTURE TODO: crate spinner animation and block UI
}
pub fn click_new_network(entry: Arc<WifiEntry>) {
@ -228,7 +228,7 @@ pub fn click_new_network(entry: Arc<WifiEntry>) {
});
});
});
// TODO crate spinner animation and block UI
// FUTURE TODO: crate spinner animation and block UI
};
let entry_imp = entry.imp();

View file

@ -4,7 +4,7 @@ use adw::subclass::preferences_row::PreferencesRowImpl;
use adw::subclass::prelude::ActionRowImpl;
use adw::ActionRow;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Image, Label};
use gtk::{Button, CompositeTemplate, Image, Label};
use re_set_lib::network::network_structures::{AccessPoint, WifiStrength};
use std::cell::RefCell;

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

@ -4,13 +4,14 @@ 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 adw::{gio, glib};
use dbus::arg::PropMap;
use dbus::{Error, Path};
use glib::{clone, PropertySet};
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,
@ -389,15 +390,8 @@ fn setup_callbacks(wifi_options: &Arc<WifiOptions>, path: Path<'static>) {
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),
);
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

@ -4,7 +4,7 @@ use adw::{
ActionRow, ComboRow, EntryRow, NavigationPage, PasswordEntryRow, PreferencesGroup, SwitchRow,
};
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Label};
use gtk::{Button, CompositeTemplate, Label};
use re_set_lib::network::connection::Connection;
use std::cell::RefCell;
use std::rc::Rc;

View file

@ -1,5 +1,4 @@
use crate::components::wifi::utils::IpProtocol;
use adw::glib;
use adw::glib::Object;
use adw::prelude::{ExpanderRowExt, PreferencesRowExt};
use glib::clone;

View file

@ -2,7 +2,7 @@ use crate::components::wifi::utils::IpProtocol;
use crate::components::wifi::wifi_route_entry;
use adw::{EntryRow, ExpanderRow};
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate};
use gtk::{Button, CompositeTemplate};
use std::cell::{Cell, RefCell};
#[derive(Default, CompositeTemplate)]

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,
};

View file

@ -5,70 +5,103 @@ use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use crate::components::audio::input::source_box::{populate_sources, SourceBox};
use crate::components::audio::output::sink_box::{populate_sinks, SinkBox};
use crate::components::base::setting_box::SettingBox;
use crate::components::base::utils::{start_audio_listener, Listeners, Position};
use crate::components::bluetooth::bluetooth_box::{
populate_conntected_bluetooth_devices, start_bluetooth_listener, BluetoothBox,
populate_connected_bluetooth_devices, BluetoothBox,
};
use crate::components::input::source_box::{populate_sources, SourceBox};
use crate::components::output::sink_box::{populate_sinks, SinkBox};
use crate::components::utils::Capabilities;
use crate::components::wifi::wifi_box::{
scan_for_wifi, show_stored_connections, start_event_listener, WifiBox,
};
use gtk::prelude::WidgetExt;
use gtk::{Align, FlowBox, FlowBoxChild, Frame};
pub const HANDLE_CONNECTIVITY_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::Connectivity) {
return;
}
pub const HANDLE_CONNECTIVITY_CLICK: fn(
&Capabilities,
Arc<Listeners>,
FlowBox,
Rc<RefCell<Position>>,
) = |capabilities: &Capabilities,
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::Connectivity) {
return;
}
reset_main.remove_all();
let mut count = 0;
if capabilities.wifi.get() {
let wifi_box = WifiBox::new(listeners.clone());
start_event_listener(listeners.clone(), wifi_box.clone());
show_stored_connections(wifi_box.clone());
scan_for_wifi(wifi_box.clone());
let wifi_frame = wrap_in_flow_box_child(SettingBox::new(&*wifi_box));
let bluetooth_box = BluetoothBox::new(listeners.clone());
populate_conntected_bluetooth_devices(bluetooth_box.clone());
start_bluetooth_listener(listeners, bluetooth_box.clone());
let bluetooth_frame = wrap_in_flow_box_child(SettingBox::new(&*bluetooth_box));
reset_main.remove_all();
reset_main.insert(&wifi_frame, -1);
reset_main.insert(&bluetooth_frame, -1);
reset_main.set_max_children_per_line(2);
};
count += 1;
}
pub const HANDLE_WIFI_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
if capabilities.bluetooth.get() {
let bluetooth_box = BluetoothBox::new(listeners.clone());
populate_connected_bluetooth_devices(listeners, bluetooth_box.clone());
let bluetooth_frame = wrap_in_flow_box_child(SettingBox::new(&*bluetooth_box));
reset_main.insert(&bluetooth_frame, -1);
count += 1;
}
reset_main.set_max_children_per_line(count);
};
pub const HANDLE_WIFI_CLICK: fn(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|_: &Capabilities,
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::Wifi) {
return;
}
reset_main.remove_all();
let wifi_box = WifiBox::new(listeners.clone());
start_event_listener(listeners, wifi_box.clone());
show_stored_connections(wifi_box.clone());
scan_for_wifi(wifi_box.clone());
let wifi_frame = wrap_in_flow_box_child(SettingBox::new(&*wifi_box));
reset_main.remove_all();
reset_main.insert(&wifi_frame, -1);
reset_main.set_max_children_per_line(1);
};
pub const HANDLE_BLUETOOTH_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::Bluetooth) {
return;
}
let bluetooth_box = BluetoothBox::new(listeners.clone());
start_bluetooth_listener(listeners, bluetooth_box.clone());
populate_conntected_bluetooth_devices(bluetooth_box.clone());
let bluetooth_frame = wrap_in_flow_box_child(SettingBox::new(&*bluetooth_box));
reset_main.remove_all();
reset_main.insert(&bluetooth_frame, -1);
reset_main.set_max_children_per_line(1);
};
pub const HANDLE_BLUETOOTH_CLICK: fn(
&Capabilities,
Arc<Listeners>,
FlowBox,
Rc<RefCell<Position>>,
) = |_: &Capabilities,
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::Bluetooth) {
return;
}
let bluetooth_box = BluetoothBox::new(listeners.clone());
populate_connected_bluetooth_devices(listeners, bluetooth_box.clone());
// start_bluetooth_listener(listeners, bluetooth_box.clone());
let bluetooth_frame = wrap_in_flow_box_child(SettingBox::new(&*bluetooth_box));
reset_main.remove_all();
reset_main.insert(&bluetooth_frame, -1);
reset_main.set_max_children_per_line(1);
};
pub const HANDLE_AUDIO_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
pub const HANDLE_AUDIO_CLICK: fn(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|_: &Capabilities,
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::Audio) {
return;
}
@ -92,8 +125,11 @@ pub const HANDLE_AUDIO_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>)
reset_main.set_max_children_per_line(2);
};
pub const HANDLE_VOLUME_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
pub const HANDLE_VOLUME_CLICK: fn(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|_: &Capabilities,
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::AudioOutput) {
return;
}
@ -109,25 +145,35 @@ pub const HANDLE_VOLUME_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>
reset_main.set_max_children_per_line(1);
};
pub const HANDLE_MICROPHONE_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::AudioInput) {
return;
}
let audio_input = Arc::new(SourceBox::new());
start_audio_listener(listeners.clone(), None, Some(audio_input.clone()));
if !listeners.pulse_listener.load(Ordering::SeqCst) {
spin_loop();
}
populate_sources(audio_input.clone());
let source_frame = wrap_in_flow_box_child(SettingBox::new(&*audio_input));
reset_main.remove_all();
reset_main.insert(&source_frame, -1);
reset_main.set_max_children_per_line(1);
};
pub const HANDLE_MICROPHONE_CLICK: fn(
&Capabilities,
Arc<Listeners>,
FlowBox,
Rc<RefCell<Position>>,
) = |_: &Capabilities,
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::AudioInput) {
return;
}
let audio_input = Arc::new(SourceBox::new());
start_audio_listener(listeners.clone(), None, Some(audio_input.clone()));
if !listeners.pulse_listener.load(Ordering::SeqCst) {
spin_loop();
}
populate_sources(audio_input.clone());
let source_frame = wrap_in_flow_box_child(SettingBox::new(&*audio_input));
reset_main.remove_all();
reset_main.insert(&source_frame, -1);
reset_main.set_max_children_per_line(1);
};
pub const HANDLE_HOME: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
pub const HANDLE_HOME: fn(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|_: &Capabilities,
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners, position, Position::Home) {
return;
}
@ -162,60 +208,3 @@ fn handle_init(
listeners.stop_bluetooth_listener();
false
}
// for future implementations
// pub const HANDLE_VPN_CLICK: fn(Arc<Listeners>, FlowBox) =
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
// listeners.stop_network_listener();
// listeners.stop_bluetooth_listener();
// listeners.stop_audio_listener();
// let label = Label::new(Some("not implemented yet"));
// resetMain.remove_all();
// resetMain.insert(&label, -1);
// resetMain.set_max_children_per_line(1);
// };
//
// pub const HANDLE_PERIPHERALS_CLICK: fn(Arc<Listeners>, FlowBox) =
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
// listeners.stop_network_listener();
// listeners.stop_audio_listener();
// listeners.stop_bluetooth_listener();
// let label = Label::new(Some("not implemented yet"));
// resetMain.remove_all();
// resetMain.insert(&label, -1);
// resetMain.set_max_children_per_line(1);
// };
//
// pub const HANDLE_MONITOR_CLICK: fn(Arc<Listeners>, FlowBox) =
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
// listeners.stop_network_listener();
// listeners.stop_audio_listener();
// listeners.stop_bluetooth_listener();
// let label = Label::new(Some("not implemented yet"));
// resetMain.remove_all();
// resetMain.insert(&label, -1);
// resetMain.set_max_children_per_line(1);
// };
//
// pub const HANDLE_MOUSE_CLICK: fn(Arc<Listeners>, FlowBox) =
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
// listeners.stop_network_listener();
// listeners.stop_audio_listener();
// listeners.stop_bluetooth_listener();
// let label = Label::new(Some("not implemented yet"));
// resetMain.remove_all();
// resetMain.insert(&label, -1);
// resetMain.set_max_children_per_line(1);
// };
//
// pub const HANDLE_KEYBOARD_CLICK: fn(Arc<Listeners>, FlowBox) =
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
// listeners.stop_network_listener();
// listeners.stop_audio_listener();
// listeners.stop_bluetooth_listener();
// let label = Label::new(Some("not implemented yet"));
// resetMain.remove_all();
// resetMain.insert(&label, -1);
// resetMain.set_max_children_per_line(1);
// };
//

View file

@ -3,3 +3,4 @@ pub mod reset_window;
pub mod reset_window_impl;
pub mod sidebar_entry;
pub mod sidebar_entry_impl;
pub mod consts;

View file

@ -1,17 +1,32 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use adw::glib::clone;
use adw::subclass::prelude::ObjectSubclassIsExt;
use adw::BreakpointCondition;
use glib::Object;
use gtk::gio::ActionEntry;
use gtk::{gio, glib, AccessibleRole, Application, ListBoxRow, Orientation, StateFlags};
use gtk::{
gio, AccessibleRole, Align, Application, FlowBox, FlowBoxChild, Frame, ListBoxRow, Orientation,
StateFlags,
};
use gtk::{prelude::*, DirectionType};
use re_set_lib::utils::plugin_setup::FRONTEND_PLUGINS;
use crate::components::base::setting_box::SettingBox;
use crate::components::base::utils::{Listeners, Position};
use crate::components::plugin::function::PluginSidebarInfo;
use crate::components::utils::get_capabilities;
use crate::components::window::handle_sidebar_click::*;
use crate::components::window::reset_window_impl;
use crate::components::window::sidebar_entry::SidebarEntry;
use crate::components::window::sidebar_entry_impl::Categories;
use crate::VERSION;
use super::consts::{
AUDIO_SIDEBAR, BLUETOOTH_SIDEBAR, CONNECTIVITY_SIDEBAR, SINK_SIDEBAR, SOURCE_SIDEBAR,
WIFI_SIDEBAR,
};
glib::wrapper! {
pub struct ReSetWindow(ObjectSubclass<reset_window_impl::ReSetWindow>)
@ -39,94 +54,6 @@ impl ReSetWindow {
window
}
pub fn setup_shortcuts(&self) {
let search_action = ActionEntry::builder("search")
.activate(move |window: &Self, _, _| {
let imp = window.imp();
if !imp.reset_overlay_split_view.shows_sidebar() {
imp.reset_overlay_split_view.set_show_sidebar(true);
}
window.imp().reset_search_entry.grab_focus();
})
.build();
let close_action = ActionEntry::builder("close")
.activate(move |window: &Self, _, _| {
window.close();
})
.build();
let vim_up = ActionEntry::builder("up")
.activate(move |window: &Self, _, _| {
window.child_focus(DirectionType::Up);
})
.build();
let vim_right = ActionEntry::builder("right")
.activate(move |window: &Self, _, _| {
window.child_focus(DirectionType::Right);
})
.build();
let vim_down = ActionEntry::builder("down")
.activate(move |window: &Self, _, _| {
window.child_focus(DirectionType::Down);
})
.build();
let vim_left = ActionEntry::builder("left")
.activate(move |window: &Self, _, _| {
window.child_focus(DirectionType::Left);
})
.build();
// let clear_initial = ActionEntry::builder("clear_initial")
// .activate(move |window: &Self, _, _| {
// let imp = window.imp();
// for (_, subentries) in imp.sidebar_entries.borrow().iter() {
// for subentry in subentries {
// if &*subentry.imp().name.borrow() == "Output" {
// subentry.set_state_flags(StateFlags::SELECTED, false);
// }
// }
// }
// })
// .build();
let about_action = ActionEntry::builder("about")
.activate(move |window: &ReSetWindow, _, _| {
let dialog = adw::AboutWindow::builder()
.application_name("ReSet")
.application_icon("ReSet")
.developer_name("Xetibo")
.license("GPL-3.0")
.license_type(gtk::License::Gpl30)
.website("https://github.com/Xetibo/ReSet")
.issue_url("https://github.com/Xetibo/ReSet/issues")
.version("0.1.8")
.transient_for(window)
.modal(true)
.copyright("© 2022-2023 Xetibo")
.developers(vec!["DashieTM".to_string(), "Takotori".to_string()])
.designers(vec!["DashieTM".to_string(), "Takotori".to_string()])
.build();
// window.imp().reset_popover_menu.popdown();
dialog.present();
})
.build();
self.add_action_entries([
search_action,
close_action,
about_action,
vim_up,
vim_right,
vim_down,
vim_left,
// clear_initial,
]);
}
pub fn handle_dynamic_sidebar(&self) {
let self_imp = self.imp();
self_imp
@ -192,110 +119,84 @@ impl ReSetWindow {
pub fn setup_sidebar_entries(&self) {
let self_imp = self.imp();
let mut sidebar_entries = self_imp.sidebar_entries.borrow_mut();
let capabilities = get_capabilities();
let wifi = capabilities.contains(&"WiFi".to_string());
let bluetooth = capabilities.contains(&"Bluetooth".to_string());
let audio = capabilities.contains(&"Audio".to_string());
self_imp.capabilities.set(wifi, bluetooth, audio);
let connectivity_list = vec![
Rc::new(SidebarEntry::new(
"WiFi",
"network-wireless-symbolic",
Categories::Connectivity,
true,
HANDLE_WIFI_CLICK,
)),
Rc::new(SidebarEntry::new(
"Bluetooth",
"bluetooth-symbolic",
Categories::Connectivity,
true,
HANDLE_BLUETOOTH_CLICK,
)),
// uncommented when VPN is implemented
// SidebarEntry::new(
// "VPN",
// "network-vpn-symbolic",
// Categories::Connectivity,
// true,
// HANDLE_VPN_CLICK,
// ),
];
let mut sidebar_list = Vec::new();
sidebar_entries.push((
Rc::new(SidebarEntry::new(
"Connectivity",
"network-wired-symbolic",
Categories::Connectivity,
false,
HANDLE_CONNECTIVITY_CLICK,
)),
connectivity_list,
));
if wifi || bluetooth {
sidebar_list.push(CONNECTIVITY_SIDEBAR);
}
if wifi {
sidebar_list.push(WIFI_SIDEBAR);
};
if bluetooth {
sidebar_list.push(BLUETOOTH_SIDEBAR);
};
if audio {
sidebar_list.push(AUDIO_SIDEBAR);
sidebar_list.push(SINK_SIDEBAR);
sidebar_list.push(SOURCE_SIDEBAR);
}
let output = Rc::new(SidebarEntry::new(
"Output",
"audio-volume-high-symbolic",
Categories::Audio,
true,
HANDLE_VOLUME_CLICK,
));
output.set_receives_default(true);
let audio_list = vec![
output,
Rc::new(SidebarEntry::new(
"Input",
"audio-input-microphone-symbolic",
Categories::Audio,
true,
HANDLE_MICROPHONE_CLICK,
)),
];
let mut plugin_sidebar_list = vec![];
unsafe {
for plugin in FRONTEND_PLUGINS.iter() {
let plugin_capabilities = &plugin.capabilities;
sidebar_entries.push((
Rc::new(SidebarEntry::new(
"Audio",
"audio-headset-symbolic",
Categories::Audio,
false,
HANDLE_AUDIO_CLICK,
)),
audio_list,
));
(plugin.frontend_startup)();
// uncommented when implemented
// let peripheralsList = vec![
// SidebarEntry::new(
// "Displays",
// "video-display-symbolic",
// Categories::Peripherals,
// true,
// HANDLE_MONITOR_CLICK,
// ),
// SidebarEntry::new(
// "Mouse",
// "input-mouse-symbolic",
// Categories::Peripherals,
// true,
// HANDLE_MOUSE_CLICK,
// ),
// SidebarEntry::new(
// "Keyboard",
// "input-keyboard-symbolic",
// Categories::Peripherals,
// true,
// HANDLE_KEYBOARD_CLICK,
// ),
// ];
let (sidebar_info, plugin_boxes) = (plugin.frontend_data)();
let listeners = self_imp.listeners.clone();
// let home = SidebarEntry::new(
// "Home",
// "preferences-system-devices-symbolic",
// Categories::Peripherals,
// false,
// HANDLE_VOLUME_CLICK,
// );
//
// sidebar_entries.push((home, Vec::new()));
if plugin_capabilities.1 {
let mut found = false;
for capability in plugin_capabilities.0.iter() {
if capabilities.contains(&capability.to_string()) {
found = true;
break;
}
}
if !found {
continue;
}
}
let event = Rc::new(
move |reset_main: FlowBox,
position: Rc<RefCell<Position>>,
boxes: Vec<gtk::Box>| {
if handle_init(
listeners.clone(),
position,
Position::Custom(String::from(sidebar_info.name)),
) {
return;
}
reset_main.remove_all();
for plugin_box in &boxes {
let frame =
wrap_in_flow_box_child(SettingBox::new(&plugin_box.clone()));
reset_main.insert(&frame, -1);
}
reset_main.set_max_children_per_line(boxes.len() as u32);
},
);
(HANDLE_VOLUME_CLICK)(
plugin_sidebar_list.push(PluginSidebarInfo {
name: sidebar_info.name,
icon_name: sidebar_info.icon_name,
parent: sidebar_info.parent,
click_event: event,
plugin_boxes,
});
}
}
HANDLE_VOLUME_CLICK(
&self_imp.capabilities,
self_imp.listeners.clone(),
self_imp.reset_main.clone(),
self_imp.position.clone(),
@ -306,41 +207,142 @@ impl ReSetWindow {
.connect_row_activated(clone!(@ weak self_imp => move |_, _| {
self_imp.reset_search_entry.set_text("");
}));
for (main_entry, sub_entries) in sidebar_entries.iter() {
self_imp.reset_sidebar_list.append(&**main_entry);
for sub_entry in sub_entries {
// TODO change this to home when home offers dynamic selection
// this is just a placeholder for now, hence hardcoded
if &*sub_entry.imp().name.borrow() == "Output" {
self_imp.reset_sidebar_list.append(&**sub_entry);
self_imp.default_entry.replace(Some(sub_entry.clone()));
sub_entry.grab_focus();
sub_entry.set_state_flags(StateFlags::SELECTED, false);
} else {
self_imp.reset_sidebar_list.append(&**sub_entry);
}
let mut i = 0;
for info in sidebar_list {
if info.parent.is_none() && i != 0 {
self_imp.reset_sidebar_list.insert(&create_separator(), i);
i += 1;
}
let separator = gtk::Separator::builder()
.margin_bottom(3)
.margin_top(3)
.orientation(Orientation::Horizontal)
.accessible_role(AccessibleRole::Separator)
.can_focus(false)
.build();
let separator_row = ListBoxRow::builder()
.child(&separator)
.selectable(false)
.activatable(false)
.can_target(false)
// .focusable(false)
.accessible_role(AccessibleRole::Separator)
.build();
// TODO how to simply skip this ?
self_imp.reset_sidebar_list.append(&separator_row);
let entry = SidebarEntry::new(&info);
self_imp.reset_sidebar_list.insert(&entry, i);
i += 1;
}
for info in plugin_sidebar_list {
if info.parent.is_none() && i != 0 {
self_imp.reset_sidebar_list.insert(&create_separator(), i);
i += 1;
}
let entry = SidebarEntry::new(&info);
self_imp.reset_sidebar_list.insert(&entry, i);
i += 1;
}
}
pub fn setup_shortcuts(&self) {
let search_action = ActionEntry::builder("search")
.activate(move |window: &Self, _, _| {
let imp = window.imp();
if !imp.reset_overlay_split_view.shows_sidebar() {
imp.reset_overlay_split_view.set_show_sidebar(true);
}
window.imp().reset_search_entry.grab_focus();
})
.build();
let banner_action = ActionEntry::builder("banner")
.parameter_type(Some(&String::static_variant_type()))
.activate(move |window: &Self, _, text| {
let imp = window.imp();
if let Some(text) = text {
imp.reset_banner.set_title(&text.to_string());
}
imp.reset_banner.set_revealed(true);
})
.build();
let close_action = ActionEntry::builder("close")
.activate(move |window: &Self, _, _| {
window.close();
})
.build();
let error_popup_action = ActionEntry::builder("show_error")
.activate(move |window: &Self, _, _| {
window.imp().error_popup.popup();
})
.build();
let error_popdown_action = ActionEntry::builder("hide_error")
.activate(move |window: &Self, _, _| {
window.imp().error_popup.popdown();
})
.build();
let vim_up = ActionEntry::builder("up")
.activate(move |window: &Self, _, _| {
window.child_focus(DirectionType::Up);
})
.build();
let vim_right = ActionEntry::builder("right")
.activate(move |window: &Self, _, _| {
window.child_focus(DirectionType::Right);
})
.build();
let vim_down = ActionEntry::builder("down")
.activate(move |window: &Self, _, _| {
window.child_focus(DirectionType::Down);
})
.build();
let vim_left = ActionEntry::builder("left")
.activate(move |window: &Self, _, _| {
window.child_focus(DirectionType::Left);
})
.build();
// let clear_initial = ActionEntry::builder("clear_initial")
// .activate(move |window: &Self, _, _| {
// let imp = window.imp();
// for (_, subentries) in imp.sidebar_entries.borrow().iter() {
// for subentry in subentries {
// if &*subentry.imp().name.borrow() == "Output" {
// subentry.set_state_flags(StateFlags::SELECTED, false);
// }
// }
// }
// })
// .build();
let about_action = ActionEntry::builder("about")
.activate(move |window: &ReSetWindow, _, _| {
let dialog = adw::AboutWindow::builder()
.application_name("ReSet")
.application_icon("ReSet")
.developer_name("Xetibo")
.license("GPL-3.0")
.license_type(gtk::License::Gpl30)
.website("https://github.com/Xetibo/ReSet")
.issue_url("https://github.com/Xetibo/ReSet/issues")
.version(VERSION)
.transient_for(window)
.modal(true)
.copyright("© 2022-2023 Xetibo")
.developers(vec!["DashieTM".to_string(), "Takotori".to_string()])
.designers(vec!["DashieTM".to_string(), "Takotori".to_string()])
.build();
// window.imp().reset_popover_menu.popdown();
dialog.present();
})
.build();
self.add_action_entries([
search_action,
banner_action,
close_action,
about_action,
vim_up,
vim_right,
vim_down,
vim_left,
error_popup_action,
error_popdown_action,
]);
}
}
fn setup_callback(window: Rc<ReSetWindow>) -> Rc<ReSetWindow> {
let self_imp = window.imp();
let activated_ref = window.clone();
@ -373,16 +375,78 @@ fn setup_callback(window: Rc<ReSetWindow>) -> Rc<ReSetWindow> {
*default_entry = None;
}
}
let click_event = result.imp().on_click_event.borrow().on_click_event;
(click_event)(
imp.listeners.clone(),
imp.reset_main.get(),
imp.position.clone(),
);
let click_event = result.imp().on_click_event.borrow();
if let Some(event) = click_event.on_click_event {
event(
&imp.capabilities,
imp.listeners.clone(),
imp.reset_main.get(),
imp.position.clone(),
);
} else {
let event = click_event.on_plugin_click_event.clone();
event(
imp.reset_main.get(),
imp.position.clone(),
result.imp().plugin_boxes.borrow().clone(),
);
}
});
self_imp.reset_close.connect_clicked(move |_| {
close_ref.close();
});
self_imp.reset_banner.connect_button_clicked(|banner| {
banner.set_revealed(false);
banner.set_title("Info");
});
window
}
pub fn create_separator() -> ListBoxRow {
let separator: gtk::Separator = gtk::Separator::builder()
.margin_bottom(3)
.margin_top(3)
.orientation(Orientation::Horizontal)
.accessible_role(AccessibleRole::Separator)
.can_focus(false)
.build();
ListBoxRow::builder()
.child(&separator)
.selectable(false)
.activatable(false)
.can_target(false)
.accessible_role(AccessibleRole::Separator)
.build()
}
fn handle_init(
listeners: Arc<Listeners>,
position: Rc<RefCell<Position>>,
clicked_position: Position,
) -> bool {
{
let mut pos_borrow = position.borrow_mut();
if *pos_borrow == clicked_position {
return true;
}
*pos_borrow = clicked_position;
}
listeners.stop_network_listener();
listeners.stop_audio_listener();
listeners.stop_bluetooth_listener();
false
}
fn wrap_in_flow_box_child(widget: SettingBox) -> FlowBoxChild {
let frame = Frame::new(None);
frame.set_child(Some(&widget));
frame.add_css_class("resetSettingFrame");
FlowBoxChild::builder()
.child(&frame)
.halign(Align::Fill)
.valign(Align::Start)
.build()
}

View file

@ -2,15 +2,17 @@ use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use adw::glib::StaticTypeExt;
use adw::subclass::prelude::AdwApplicationWindowImpl;
use adw::{Breakpoint, OverlaySplitView};
use glib::prelude::StaticTypeExt;
use glib::subclass::InitializingObject;
use gtk::prelude::WidgetExt;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, FlowBox, ListBox, SearchEntry};
use gtk::{Button, CompositeTemplate, FlowBox, ListBox, SearchEntry};
use crate::components::base::error::ReSetError;
use crate::components::base::utils::{Listeners, Position};
use crate::components::utils::Capabilities;
use crate::components::wifi::wifi_box::WifiBox;
use crate::components::window::reset_window;
use crate::components::window::sidebar_entry::SidebarEntry;
@ -23,6 +25,8 @@ pub struct ReSetWindow {
#[template_child]
pub reset_main: TemplateChild<FlowBox>,
#[template_child]
pub reset_banner: TemplateChild<adw::Banner>,
#[template_child]
pub reset_sidebar_breakpoint: TemplateChild<Breakpoint>,
#[template_child]
pub reset_overlay_split_view: TemplateChild<OverlaySplitView>,
@ -34,10 +38,14 @@ pub struct ReSetWindow {
pub reset_sidebar_toggle: TemplateChild<Button>,
#[template_child]
pub reset_close: TemplateChild<Button>,
// #[template_child]
// pub reset_banner: TemplateChild<adw::Banner>,
pub sidebar_entries: SidebarEntries,
pub default_entry: RefCell<Option<Rc<SidebarEntry>>>,
pub listeners: Arc<Listeners>,
pub position: Rc<RefCell<Position>>,
pub error_popup: ReSetError,
pub capabilities: Capabilities,
}
unsafe impl Send for ReSetWindow {}
@ -77,14 +85,20 @@ impl WidgetImpl for ReSetWindow {
if width > 658 {
self.reset_main.set_margin_start(60);
self.reset_main.set_margin_end(60);
self.reset_banner.set_margin_start(60);
self.reset_banner.set_margin_end(60);
} else {
let div = (width - 540) / 2;
if div > 1 {
self.reset_main.set_margin_start(div);
self.reset_main.set_margin_end(div);
self.reset_banner.set_margin_start(div);
self.reset_banner.set_margin_end(div);
} else {
self.reset_main.set_margin_start(0);
self.reset_main.set_margin_end(0);
self.reset_banner.set_margin_start(0);
self.reset_banner.set_margin_end(0);
}
}
}

View file

@ -1,14 +1,9 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use crate::components::base::utils::{Listeners, Position};
use crate::components::plugin::function::TSideBarInfo;
use crate::components::window::sidebar_entry_impl;
use crate::components::window::sidebar_entry_impl::{Categories, SidebarAction};
use crate::components::window::sidebar_entry_impl::SidebarAction;
use adw::subclass::prelude::ObjectSubclassIsExt;
use glib::Object;
use gtk::prelude::*;
use gtk::{glib, FlowBox};
glib::wrapper! {
pub struct SidebarEntry(ObjectSubclass<sidebar_entry_impl::SidebarEntry>)
@ -17,37 +12,35 @@ glib::wrapper! {
}
impl SidebarEntry {
pub fn new(
entry_name: &str,
icon_name: &str,
category: Categories,
is_subcategory: bool,
click_event: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>),
) -> Self {
pub fn new<T: TSideBarInfo>(info: &T) -> Self {
let entry: SidebarEntry = Object::builder().build();
let entry_imp = entry.imp();
entry_imp.reset_sidebar_label.get().set_text(entry_name);
entry_imp.reset_sidebar_label.get().set_text(info.name());
entry_imp
.reset_sidebar_image
.set_from_icon_name(Some(icon_name));
entry_imp.category.set(category);
entry_imp.is_subcategory.set(is_subcategory);
.set_from_icon_name(Some(info.icon_name()));
if let Some(boxes) = info.plugin_boxes() {
entry_imp.plugin_boxes.borrow_mut().extend(boxes);
}
match &info.parent() {
None => {}
Some(parent) => {
let mut name = entry_imp.parent.borrow_mut();
*name = parent.to_string();
entry.child().unwrap().set_margin_start(30);
}
}
{
let mut name = entry_imp.name.borrow_mut();
*name = String::from(entry_name);
*name = info.name().to_string();
let mut action = entry_imp.on_click_event.borrow_mut();
*action = SidebarAction {
on_click_event: click_event,
on_click_event: info.regular_click_event(),
on_plugin_click_event: info.plugin_click_event(),
};
}
Self::set_margin(&entry);
entry
}
fn set_margin(entry: &SidebarEntry) {
if entry.imp().is_subcategory.get() {
let option = entry.child().unwrap();
option.set_margin_start(30);
}
}
}

View file

@ -1,20 +1,20 @@
use std::cell::{Cell, RefCell};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use glib::subclass::InitializingObject;
use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate, FlowBox, Image, Label, ListBoxRow};
use gtk::{CompositeTemplate, Image, Label, ListBoxRow};
use crate::components::base::utils::{Listeners, Position};
use crate::components::plugin::function::{PluginClickEvent, RegularClickEvent};
use crate::components::window::handle_sidebar_click::HANDLE_HOME;
use crate::components::window::sidebar_entry;
#[derive(Default)]
pub enum Categories {
Connectivity,
Audio,
Peripherals,
// FUTURE TODO: are these ever used ?
// Connectivity,
// Audio,
// Peripherals,
#[default]
Misc,
}
@ -26,20 +26,22 @@ pub struct SidebarEntry {
pub reset_sidebar_label: TemplateChild<Label>,
#[template_child]
pub reset_sidebar_image: TemplateChild<Image>,
pub category: Cell<Categories>,
pub is_subcategory: Cell<bool>,
pub parent: RefCell<String>,
pub on_click_event: RefCell<SidebarAction>,
pub plugin_boxes: RefCell<Vec<gtk::Box>>,
pub name: RefCell<String>,
}
pub struct SidebarAction {
pub on_click_event: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>),
pub on_click_event: Option<RegularClickEvent>,
pub on_plugin_click_event: PluginClickEvent,
}
impl Default for SidebarAction {
fn default() -> Self {
Self {
on_click_event: HANDLE_HOME,
on_click_event: Some(HANDLE_HOME),
on_plugin_click_event: Rc::new(|_, _, _| {}),
}
}
}

View file

@ -1,3 +1,6 @@
use std::hint::{self};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
@ -11,12 +14,21 @@ use gtk::{gio, Application, CssProvider};
use reset_daemon::run_daemon;
mod components;
mod tests;
const APP_ID: &str = "org.Xetibo.ReSet";
/// Version of the current package.
/// Use this to avoid version mismatch conflicts.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[tokio::main]
async fn main() {
tokio::task::spawn(daemon_check());
let ready = Arc::new(AtomicBool::new(false));
tokio::task::spawn(daemon_check(ready.clone()));
while !ready.load(std::sync::atomic::Ordering::SeqCst) {
hint::spin_loop();
}
gio::resources_register_include!("src.templates.gresource")
.expect("Failed to register resources.");
gio::resources_register_include!("src.icons.gresource").expect("Failed to register resources.");
@ -59,8 +71,8 @@ fn shutdown(_: &Application) {
});
}
async fn daemon_check() {
let handle = thread::spawn(|| {
async fn daemon_check(ready: Arc<AtomicBool>) {
let handle = thread::spawn(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(100));
let res: Result<(), Error> = proxy.method_call(BASE, "RegisterClient", ("ReSet",));
@ -68,6 +80,8 @@ async fn daemon_check() {
});
let res = handle.join();
if res.unwrap().is_err() {
run_daemon().await;
run_daemon(Some(ready)).await;
} else {
ready.store(true, Ordering::SeqCst);
}
}

View file

@ -1,11 +1,14 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.17.0 -->
<!-- Created with Cambalache 0.90.1 -->
<interface>
<requires lib="gtk" version="4.12"/>
<requires lib="libadwaita" version="1.4"/>
<template class="resetAudioInput" parent="GtkBox">
<property name="orientation">vertical</property>
<property name="valign">start</property>
<child>
<object class="resetError" id="error"/>
</child>
<child>
<object class="GtkLabel">
<property name="css-classes">resetSettingLabel</property>
@ -19,7 +22,7 @@
<object class="AdwNavigationView">
<child>
<object class="AdwNavigationPage">
<property name="tag">output</property>
<property name="tag">audiostreams</property>
<property name="title">output</property>
<child>
<object class="GtkBox">
@ -125,7 +128,7 @@
</child>
<child>
<object class="AdwNavigationPage">
<property name="tag">sources</property>
<property name="tag">devices</property>
<property name="title">sources</property>
<child>
<object class="GtkBox">

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