Compare commits

..

No commits in common. "main" and "0.1.3" have entirely different histories.
main ... 0.1.3

132 changed files with 3914 additions and 10344 deletions

View file

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

View file

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

2
.envrc
View file

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

View file

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

View file

@ -1,6 +1,7 @@
on: on:
release: release:
types: [created] types: [ created ]
jobs: jobs:
release: release:
runs-on: [self-hosted, ubuntu] runs-on: [self-hosted, ubuntu]
@ -12,26 +13,23 @@ jobs:
profile: minimal profile: minimal
toolchain: nightly toolchain: nightly
- name: Build rust package - name: Build rust package
run: | run: cargo build --release --verbose
cargo build --release --verbose
- name: Build Flatpak - 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" 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
- name: Build Ubuntu package - name: Build Ubuntu package
run: | run: |
mkdir -p ./debian/usr cp ./target/release/reset ./debian/.
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 dpkg-deb --build debian
mv debian.deb ReSet.deb mv debian.deb reset.deb
- name: Release - name: Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
files: | files: |
target/release/ReSet target/release/reset
flatpak/reset.flatpak flatpak/reset.flatpak
ReSet.deb reset.deb

View file

@ -1,33 +1,25 @@
# inspired by https://github.com/danth/stylix/blob/master/.github/workflows/docs.yml name: Rust
name: Rust-build
on: on:
push: push:
branches: ["main"] branches: [ "main" ]
pull_request: pull_request:
branches: ["main"] branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs: jobs:
build: build:
name: Build runs-on: [self-hosted, ubuntu]
permissions:
contents: read
runs-on: ubuntu-latest
steps: steps:
- name: Install Nix - uses: actions/checkout@v3
uses: DeterminateSystems/nix-installer-action@main - name: nightly-rust
uses: actions-rs/toolchain@v1
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} profile: minimal
extra-conf: | toolchain: nightly
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 - name: Build
run: nix -L build github:${{ github.repository }}/${{ github.sha }} --no-write-lock-file run: cargo build --verbose
- name: Run clippy
run: cargo clippy --fix

4
.gitignore vendored
View file

@ -9,6 +9,10 @@ flatpak/reset.flatpak
pkg/ pkg/
*.pkg.tar.zst *.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 # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk

1434
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

140
README.md
View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View file

@ -1,9 +1,5 @@
Package: ReSet Package: ReSet
Version: 2.0.0 Version: 0.1
Maintainer: DashieTM Maintainer: DashieTM
Architecture: all Architecture: all
Description: A wip universal Linux settings application. 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

View file

@ -1,83 +0,0 @@
{
"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
}

View file

@ -1,62 +0,0 @@
{
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,3 +1,4 @@
#! /bin/bash
python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json
flatpak-builder build org.Xetibo.ReSet.json --force-clean flatpak-builder build org.Xetibo.ReSet.json --force-clean
flatpak build-export export build 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

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

View file

@ -1,12 +1,12 @@
{ {
"app-id": "org.Xetibo.ReSet", "app-id": "org.Xetibo.ReSet",
"runtime": "org.gnome.Platform", "runtime": "org.gnome.Platform",
"runtime-version": "46", "runtime-version": "45",
"sdk": "org.gnome.Sdk", "sdk": "org.gnome.Sdk",
"sdk-extensions": [ "sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-nightly" "org.freedesktop.Sdk.Extension.rust-stable"
], ],
"command": "ReSet", "command": "reset",
"finish-args": [ "finish-args": [
"--socket=pulseaudio", "--socket=pulseaudio",
"--share=network", "--share=network",
@ -17,11 +17,10 @@
"--device=all", "--device=all",
"--allow=bluetooth", "--allow=bluetooth",
"--socket=system-bus", "--socket=system-bus",
"--socket=session-bus", "--socket=session-bus"
"--persist=~/.config/reset:create"
], ],
"build-options": { "build-options": {
"append-path": "/usr/lib/sdk/rust-nightly/bin" "append-path": "/usr/lib/sdk/rust-stable/bin"
}, },
"modules": [ "modules": [
{ {
@ -35,7 +34,7 @@
"build-commands": [ "build-commands": [
"cargo --offline fetch --manifest-path Cargo.toml --verbose", "cargo --offline fetch --manifest-path Cargo.toml --verbose",
"cargo --offline build --release --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 ./src/resources/icons/ReSet.svg /app/share/icons/hicolor/scalable/apps/org.Xetibo.ReSet.svg",
"install -Dm644 ./flatpak/org.Xetibo.ReSet.desktop /app/share/applications/org.Xetibo.ReSet.desktop" "install -Dm644 ./flatpak/org.Xetibo.ReSet.desktop /app/share/applications/org.Xetibo.ReSet.desktop"
], ],

View file

@ -1,13 +1,17 @@
{ {
"app-id": "org.Xetibo.ReSet", "app-id": "org.Xetibo.ReSet",
"runtime": "org.gnome.Platform", "runtime": "org.gnome.Platform",
"runtime-version": "46", "runtime-version": "45",
"sdk": "org.gnome.Sdk", "sdk": "org.gnome.Sdk",
"sdk-extensions": [ "sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-nightly" "org.freedesktop.Sdk.Extension.rust-stable"
], ],
"command": "ReSet", "command": "reset",
"finish-args": [ "finish-args": [
"--talk-name=org.Xetibo.ReSetDaemon",
"--system-talk-name=org.freedesktop.NetworkManager",
"--system-talk-name=org.freedesktop.DBus",
"--system-talk-name=org.bluez",
"--socket=pulseaudio", "--socket=pulseaudio",
"--share=network", "--share=network",
"--share=ipc", "--share=ipc",
@ -15,12 +19,10 @@
"--socket=wayland", "--socket=wayland",
"--device=dri", "--device=dri",
"--device=all", "--device=all",
"--allow=bluetooth", "--allow=bluetooth"
"--socket=system-bus",
"--socket=session-bus"
], ],
"build-options": { "build-options": {
"append-path": "/usr/lib/sdk/rust-nightly/bin" "append-path": "/usr/lib/sdk/rust-stable/bin"
}, },
"modules": [ "modules": [
{ {
@ -33,8 +35,8 @@
}, },
"build-commands": [ "build-commands": [
"cargo --offline fetch --manifest-path Cargo.toml --verbose", "cargo --offline fetch --manifest-path Cargo.toml --verbose",
"cargo --offline build --verbose", "cargo --offline build --release --verbose",
"install -Dm755 ./target/debug/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 ./src/resources/icons/ReSet.svg /app/share/icons/hicolor/scalable/apps/org.Xetibo.ReSet.svg",
"install -Dm644 ./flatpak/org.Xetibo.ReSet.desktop /app/share/applications/org.Xetibo.ReSet.desktop" "install -Dm644 ./flatpak/org.Xetibo.ReSet.desktop /app/share/applications/org.Xetibo.ReSet.desktop"
], ],

View file

@ -1,69 +0,0 @@
{
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";
};
}

View file

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

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

View file

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

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

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

View file

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,19 +1,19 @@
use std::time::Duration; use std::time::Duration;
use adw::glib;
use adw::glib::Object; use adw::glib::Object;
use adw::prelude::{ComboRowExt, PreferencesRowExt}; use adw::prelude::{ComboRowExt, PreferencesRowExt};
use dbus::blocking::Connection; use dbus::blocking::Connection;
use dbus::Error; use dbus::Error;
use glib::clone;
use glib::prelude::Cast;
use glib::subclass::types::ObjectSubclassIsExt; use glib::subclass::types::ObjectSubclassIsExt;
use glib::{clone, Cast};
use gtk::{gio, StringList, StringObject}; use gtk::{gio, StringList, StringObject};
use components::utils::create_dropdown_label_factory; use components::utils::create_dropdown_label_factory;
use re_set_lib::audio::audio_structures::Card; use re_set_lib::audio::audio_structures::Card;
use crate::components; use crate::components;
use crate::components::utils::{AUDIO, BASE, DBUS_PATH}; use crate::components::utils::{BASE, DBUS_PATH, AUDIO};
use super::card_entry_impl; use super::card_entry_impl;
@ -66,7 +66,11 @@ impl CardEntry {
fn set_card_profile_of_device(device_index: u32, profile_name: String) -> bool { fn set_card_profile_of_device(device_index: u32, profile_name: String) -> bool {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap(); 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( let _: Result<(), Error> = proxy.method_call(
AUDIO, AUDIO,
"SetCardProfileOfDevice", "SetCardProfileOfDevice",

View file

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

View file

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

@ -1,74 +0,0 @@
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 crate::components::base::list_entry_impl;
use adw::glib::Object; use adw::glib;
use glib::prelude::IsA; use adw::glib::{IsA, Object};
use gtk::prelude::ListBoxRowExt; use gtk::prelude::ListBoxRowExt;
use gtk::Widget; use gtk::Widget;

View file

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

View file

@ -1,7 +1,5 @@
pub mod card_entry; pub mod card_entry;
pub mod card_entry_impl; pub mod card_entry_impl;
pub mod error;
pub mod error_impl;
pub mod list_entry; pub mod list_entry;
pub mod list_entry_impl; pub mod list_entry_impl;
pub mod popup; pub mod popup;

View file

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

View file

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

View file

@ -1,12 +1,12 @@
use crate::components::base::setting_box_impl; use crate::components::base::setting_box_impl;
use adw::glib::Object; use adw::glib;
use glib::prelude::IsA; use adw::glib::{IsA, Object};
use gtk::prelude::BoxExt; use gtk::prelude::BoxExt;
use gtk::Widget; use gtk::Widget;
glib::wrapper! { glib::wrapper! {
pub struct SettingBox(ObjectSubclass<setting_box_impl::SettingBox>) pub struct SettingBox(ObjectSubclass<setting_box_impl::SettingBox>)
@extends gtk::Box, Widget, @extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
} }

View file

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

View file

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

View file

@ -2,37 +2,25 @@ use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use adw::glib;
use adw::glib::Object; use adw::glib::Object;
use adw::prelude::{ComboRowExt, PreferencesGroupExt}; use adw::prelude::{ComboRowExt, PreferencesGroupExt};
use adw::subclass::prelude::ObjectSubclassIsExt; use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection; use dbus::blocking::Connection;
use dbus::message::SignalArgs; use dbus::message::SignalArgs;
use dbus::{Error, Path}; use dbus::{Error, Path};
use glib::prelude::Cast; use glib::{clone, Cast};
use glib::property::PropertySet;
use glib::{clone, ControlFlow};
use gtk::glib::Variant; use gtk::glib::Variant;
use gtk::prelude::{ActionableExt, ButtonExt, ListBoxRowExt, WidgetExt}; use gtk::prelude::{ActionableExt, ButtonExt, ListBoxRowExt, WidgetExt};
use gtk::{gio, StringObject}; use gtk::{gio, StringObject};
use re_set_lib::{ use re_set_lib::bluetooth::bluetooth_structures::{BluetoothAdapter, BluetoothDevice};
bluetooth::bluetooth_structures::{BluetoothAdapter, BluetoothDevice}, use re_set_lib::signals::{BluetoothDeviceAdded, BluetoothDeviceChanged, BluetoothDeviceRemoved};
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::base::utils::Listeners;
use crate::components::bluetooth::bluetooth_box_impl; use crate::components::bluetooth::bluetooth_box_impl;
use crate::components::bluetooth::bluetooth_entry::BluetoothEntry; use crate::components::bluetooth::bluetooth_entry::BluetoothEntry;
use crate::components::utils::{BASE, BLUETOOTH, DBUS_PATH}; use crate::components::utils::{BASE, BLUETOOTH, DBUS_PATH};
use super::bluetooth_event_handlers::{
device_added_handler, device_changed_handler, device_removed_handler,
};
glib::wrapper! { glib::wrapper! {
pub struct BluetoothBox(ObjectSubclass<bluetooth_box_impl::BluetoothBox>) pub struct BluetoothBox(ObjectSubclass<bluetooth_box_impl::BluetoothBox>)
@extends gtk::Box, gtk::Widget, @extends gtk::Box, gtk::Widget,
@ -42,14 +30,6 @@ glib::wrapper! {
unsafe impl Send for BluetoothBox {} unsafe impl Send for BluetoothBox {}
unsafe impl Sync 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 { impl BluetoothBox {
pub fn new(listeners: Arc<Listeners>) -> Arc<Self> { pub fn new(listeners: Arc<Listeners>) -> Arc<Self> {
let obj: Arc<Self> = Arc::new(Object::builder().build()); let obj: Arc<Self> = Arc::new(Object::builder().build());
@ -57,7 +37,7 @@ impl BluetoothBox {
} }
} }
// FUTURE TODO: // TODO
// handle bonded -> this means saved but not connected // handle bonded -> this means saved but not connected
// handle rssi below x -> don't show device // handle rssi below x -> don't show device
@ -68,7 +48,6 @@ fn setup_callbacks(
let bluetooth_box_ref = bluetooth_box.clone(); let bluetooth_box_ref = bluetooth_box.clone();
let listeners_ref = listeners.clone(); let listeners_ref = listeners.clone();
let imp = bluetooth_box.imp(); let imp = bluetooth_box.imp();
imp.reset_switch_initial.set(true);
imp.reset_visibility.set_activatable(true); imp.reset_visibility.set_activatable(true);
imp.reset_visibility imp.reset_visibility
.set_action_name(Some("navigation.push")); .set_action_name(Some("navigation.push"));
@ -88,107 +67,78 @@ fn setup_callbacks(
.store(true, Ordering::SeqCst); .store(true, Ordering::SeqCst);
}); });
let bluetooth_box_discover = bluetooth_box.clone();
imp.reset_bluetooth_discoverable_switch imp.reset_bluetooth_discoverable_switch
.connect_active_notify(clone!(@weak imp => move |state| { .connect_active_notify(clone!(@weak imp => move |state| {
set_bluetooth_adapter_visibility( set_bluetooth_adapter_visibility(imp.reset_current_bluetooth_adapter.borrow().path.clone(), state.is_active());
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 imp.reset_bluetooth_pairable_switch
.connect_active_notify(clone!(@weak imp => move |state| { .connect_active_notify(clone!(@weak imp => move |state| {
set_bluetooth_adapter_pairability( set_bluetooth_adapter_pairability(imp.reset_current_bluetooth_adapter.borrow().path.clone(), state.is_active());
imp.reset_current_bluetooth_adapter.borrow().path.clone(),
state.is_active(),
bluetooth_box_pairable.clone()
);
})); }));
imp.reset_bluetooth_switch imp.reset_bluetooth_switch
.connect_state_set(move |_, state| { .connect_state_set(move |_, state| {
bluetooth_enabled_switch_handler( if !state {
state, let imp = bluetooth_box_ref.imp();
bluetooth_box_ref.clone(), let mut available_devices = imp.available_devices.borrow_mut();
listeners_ref.clone(), 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);
set_adapter_enabled(
imp.reset_current_bluetooth_adapter.borrow().path.clone(),
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 || {
if set_adapter_enabled(
restart_ref
.imp()
.reset_current_bluetooth_adapter
.borrow()
.path
.clone(),
true,
) {
start_bluetooth_listener(restart_listener_ref.clone(), restart_ref.clone());
}
});
}
glib::Propagation::Proceed
}); });
bluetooth_box bluetooth_box
} }
fn bluetooth_enabled_switch_handler( pub fn populate_conntected_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) {
state: bool, // TODO handle saved devices -> they also exist
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 || { gio::spawn_blocking(move || {
let ref_box = bluetooth_box.clone(); let ref_box = bluetooth_box.clone();
let adapters = get_bluetooth_adapters(ref_box.clone()); let devices = get_connected_devices();
let devices = get_bluetooth_devices(ref_box.clone()); let adapters = get_bluetooth_adapters();
{ {
let imp = bluetooth_box.imp(); let imp = bluetooth_box.imp();
let list = imp.reset_model_list.write().unwrap(); let list = imp.reset_model_list.write().unwrap();
@ -205,10 +155,8 @@ pub fn populate_connected_bluetooth_devices(
*model_index += 1; *model_index += 1;
} }
} }
start_bluetooth_listener(listeners, ref_box.clone());
glib::spawn_future(async move { glib::spawn_future(async move {
glib::idle_add_once(move || { glib::idle_add_once(move || {
let new_adapter_ref = ref_box.clone();
let imp = ref_box.imp(); let imp = ref_box.imp();
let list = imp.reset_model_list.read().unwrap(); let list = imp.reset_model_list.read().unwrap();
@ -221,41 +169,44 @@ pub fn populate_connected_bluetooth_devices(
{ {
let current_adapter = imp.reset_current_bluetooth_adapter.borrow(); let current_adapter = imp.reset_current_bluetooth_adapter.borrow();
let powered = current_adapter.powered; imp.reset_bluetooth_switch
imp.reset_bluetooth_switch.set_state(powered); .set_state(current_adapter.powered);
imp.reset_bluetooth_switch.set_active(powered);
imp.reset_bluetooth_discoverable_switch imp.reset_bluetooth_discoverable_switch
.set_active(current_adapter.discoverable); .set_active(current_adapter.discoverable);
imp.reset_bluetooth_pairable_switch imp.reset_bluetooth_pairable_switch
.set_active(current_adapter.pairable); .set_active(current_adapter.pairable);
imp.reset_switch_initial.set(false);
} }
imp.reset_bluetooth_adapter imp.reset_bluetooth_adapter.connect_selected_notify(
.connect_selected_notify(move |dropdown| { clone!(@weak imp => move |dropdown| {
select_bluetooth_adapter_handler(dropdown, new_adapter_ref.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 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());
}),
);
for device in devices { for device in devices {
let path = device.path.clone(); let path = device.path.clone();
let connected = device.connected; let connected = device.connected;
let rssi = device.rssi; let bluetooth_entry = BluetoothEntry::new(device);
let bluetooth_entry = BluetoothEntry::new(device, ref_box.clone()); imp.available_devices
.borrow_mut()
.insert(path, bluetooth_entry.clone());
if connected { if connected {
imp.reset_bluetooth_connected_devices.add(&*bluetooth_entry); 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 { } else {
imp.reset_bluetooth_available_devices.add(&*bluetooth_entry); imp.reset_bluetooth_available_devices.add(&*bluetooth_entry);
imp.available_devices
.borrow_mut()
.insert(path, bluetooth_entry.clone());
} }
} }
}); });
@ -263,38 +214,11 @@ pub fn populate_connected_bluetooth_devices(
}); });
} }
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>) { pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<BluetoothBox>) {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
if listeners.bluetooth_listener.load(Ordering::SeqCst) { if listeners.bluetooth_listener.load(Ordering::SeqCst) {
return; return;
} }
let imp = bluetooth_box.imp();
if !imp.reset_current_bluetooth_adapter.borrow().powered {
return;
}
let device_added_box = bluetooth_box.clone(); let device_added_box = bluetooth_box.clone();
let device_removed_box = bluetooth_box.clone(); let device_removed_box = bluetooth_box.clone();
@ -303,11 +227,10 @@ pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<Bl
let conn = Connection::new_session().unwrap(); 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 res: Result<(), Error> = proxy.method_call(BLUETOOTH, "StartBluetoothListener", ()); let _: Result<(), Error> = proxy.method_call(BLUETOOTH, "StartBluetoothListener", ());
if res.is_err() { loop_box
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to start bluetooth listener"); .imp()
} .reset_bluetooth_available_devices
imp.reset_bluetooth_available_devices
.set_description(Some("Scanning...")); .set_description(Some("Scanning..."));
let device_added = let device_added =
BluetoothDeviceAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))) BluetoothDeviceAdded::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
@ -320,205 +243,197 @@ pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<Bl
.static_clone(); .static_clone();
let res = conn.add_match(device_added, move |ir: BluetoothDeviceAdded, _, _| { let res = conn.add_match(device_added, move |ir: BluetoothDeviceAdded, _, _| {
device_added_handler(device_added_box.clone(), ir) 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
}); });
if res.is_err() { if res.is_err() {
ERROR!( println!("fail on bluetooth device add event");
"fail on bluetooth device add event",
ErrorLevel::PartialBreakage
);
return; return;
} }
let res = conn.add_match(device_removed, move |ir: BluetoothDeviceRemoved, _, _| { let res = conn.add_match(device_removed, move |ir: BluetoothDeviceRemoved, _, _| {
device_removed_handler(device_removed_box.clone(), ir) 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
}); });
if res.is_err() { if res.is_err() {
ERROR!( println!("fail on bluetooth device remove event");
"fail on bluetooth device remove event",
ErrorLevel::PartialBreakage
);
return; return;
} }
let res = conn.add_match(device_changed, move |ir: BluetoothDeviceChanged, _, _| { let res = conn.add_match(device_changed, move |ir: BluetoothDeviceChanged, _, _| {
device_changed_handler(device_changed_box.clone(), ir) 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
}); });
if res.is_err() { if res.is_err() {
ERROR!( println!("fail on bluetooth device remove event");
"fail on bluetooth device remove event",
ErrorLevel::PartialBreakage
);
return; return;
} }
listeners.bluetooth_listener.store(true, Ordering::SeqCst); listeners.bluetooth_listener.store(true, Ordering::SeqCst);
let time = SystemTime::now(); let mut time = SystemTime::now();
let listener_active = true; let mut listener_active = true;
bluetooth_listener_loop( loop {
&conn, let _ = conn.process(Duration::from_millis(1000));
listeners, if !listeners.bluetooth_listener.load(Ordering::SeqCst) {
proxy, let _: Result<(), Error> =
bluetooth_box, proxy.method_call(BLUETOOTH, "StopBluetoothListener", ());
loop_box, loop_box
listener_active, .imp()
time, .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();
}
}
}); });
} }
fn bluetooth_listener_loop( fn get_connected_devices() -> Vec<BluetoothDevice> {
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 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 res: Result<(Vec<BluetoothDevice>,), Error> = let res: Result<(Vec<BluetoothDevice>,), Error> =
proxy.method_call(BLUETOOTH, "GetBluetoothDevices", ()); proxy.method_call(BLUETOOTH, "GetConnectedBluetoothDevices", ());
if res.is_err() { if res.is_err() {
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to get bluetooth devices");
return Vec::new(); return Vec::new();
} }
res.unwrap().0 res.unwrap().0
} }
fn get_bluetooth_adapters(bluetooth_box: Arc<BluetoothBox>) -> Vec<BluetoothAdapter> { fn get_bluetooth_adapters() -> Vec<BluetoothAdapter> {
let conn = Connection::new_session().unwrap(); 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 res: Result<(Vec<BluetoothAdapter>,), Error> = let res: Result<(Vec<BluetoothAdapter>,), Error> =
proxy.method_call(BLUETOOTH, "GetBluetoothAdapters", ()); proxy.method_call(BLUETOOTH, "GetBluetoothAdapters", ());
if res.is_err() { if res.is_err() {
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to get bluetooth adapters");
return Vec::new(); return Vec::new();
} }
res.unwrap().0 res.unwrap().0
} }
fn set_bluetooth_adapter(path: Path<'static>, bluetooth_box: Arc<BluetoothBox>) { fn set_bluetooth_adapter(path: Path<'static>) {
let conn = Connection::new_session().unwrap(); 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 res: Result<(Path<'static>,), Error> = let _: Result<(Path<'static>,), Error> =
proxy.method_call(BLUETOOTH, "SetBluetoothAdapter", (path,)); 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( fn set_bluetooth_adapter_visibility(path: Path<'static>, visible: bool) {
path: Path<'static>,
visible: bool,
bluetooth_box: Arc<BluetoothBox>,
) {
let conn = Connection::new_session().unwrap(); 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 res: Result<(bool,), Error> = proxy.method_call( let _: Result<(bool,), Error> = proxy.method_call(
BLUETOOTH, BLUETOOTH,
"SetBluetoothAdapterDiscoverability", "SetBluetoothAdapterDiscoverability",
(path, visible), (path, visible),
); );
if res.is_err() {
show_error::<BluetoothBox>(
bluetooth_box.clone(),
"Failed to set bluetooth adapter visibility",
);
}
} }
fn set_bluetooth_adapter_pairability( fn set_bluetooth_adapter_pairability(path: Path<'static>, visible: bool) {
path: Path<'static>,
visible: bool,
bluetooth_box: Arc<BluetoothBox>,
) {
let conn = Connection::new_session().unwrap(); 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 res: Result<(bool,), Error> = let _: Result<(bool,), Error> =
proxy.method_call(BLUETOOTH, "SetBluetoothAdapterPairability", (path, visible)); 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( fn set_adapter_enabled(path: Path<'static>, enabled: bool) -> bool {
path: Path<'static>,
enabled: bool,
bluetooth_box: Arc<BluetoothBox>,
) -> bool {
let conn = Connection::new_session().unwrap(); 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: Result<(bool,), Error> = let result: Result<(bool,), Error> =
proxy.method_call(BLUETOOTH, "SetBluetoothAdapterEnabled", (path, enabled)); proxy.method_call(BLUETOOTH, "SetBluetoothAdapterEnabled", (path, enabled));
if result.is_err() { if result.is_err() {
show_error::<BluetoothBox>(bluetooth_box.clone(), "Failed to enable bluetooth adapter");
return false; return false;
} }
result.unwrap().0 result.unwrap().0

View file

@ -1,15 +1,13 @@
use adw::{ActionRow, ComboRow, PreferencesGroup, SwitchRow}; use adw::{ActionRow, ComboRow, PreferencesGroup, SwitchRow};
use dbus::Path; use dbus::Path;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, Switch};
use gtk::{prelude::*, StringList}; use gtk::{prelude::*, StringList};
use gtk::{Button, CompositeTemplate, Switch};
use re_set_lib::bluetooth::bluetooth_structures::BluetoothAdapter; use re_set_lib::bluetooth::bluetooth_structures::BluetoothAdapter;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crate::components::base::error::ReSetError;
use crate::components::base::list_entry::ListEntry; use crate::components::base::list_entry::ListEntry;
use crate::components::bluetooth::bluetooth_box; use crate::components::bluetooth::bluetooth_box;
use crate::components::bluetooth::bluetooth_entry::BluetoothEntry; use crate::components::bluetooth::bluetooth_entry::BluetoothEntry;
@ -24,8 +22,6 @@ pub struct BluetoothBox {
#[template_child] #[template_child]
pub reset_bluetooth_available_devices: TemplateChild<PreferencesGroup>, pub reset_bluetooth_available_devices: TemplateChild<PreferencesGroup>,
#[template_child] #[template_child]
pub reset_bluetooth_saved_devices: TemplateChild<PreferencesGroup>,
#[template_child]
pub reset_bluetooth_refresh_button: TemplateChild<Button>, pub reset_bluetooth_refresh_button: TemplateChild<Button>,
#[template_child] #[template_child]
pub reset_bluetooth_adapter: TemplateChild<ComboRow>, pub reset_bluetooth_adapter: TemplateChild<ComboRow>,
@ -39,16 +35,12 @@ pub struct BluetoothBox {
pub reset_bluetooth_discoverable_switch: TemplateChild<SwitchRow>, pub reset_bluetooth_discoverable_switch: TemplateChild<SwitchRow>,
#[template_child] #[template_child]
pub reset_bluetooth_pairable_switch: TemplateChild<SwitchRow>, pub reset_bluetooth_pairable_switch: TemplateChild<SwitchRow>,
#[template_child]
pub error: TemplateChild<ReSetError>,
pub available_devices: BluetoothMap, pub available_devices: BluetoothMap,
pub connected_devices: BluetoothMap, pub connected_devices: BluetoothMap,
pub saved_devices: BluetoothMap,
pub reset_bluetooth_adapters: Arc<RwLock<HashMap<String, (BluetoothAdapter, u32)>>>, pub reset_bluetooth_adapters: Arc<RwLock<HashMap<String, (BluetoothAdapter, u32)>>>,
pub reset_current_bluetooth_adapter: Arc<RefCell<BluetoothAdapter>>, pub reset_current_bluetooth_adapter: Arc<RefCell<BluetoothAdapter>>,
pub reset_model_list: Arc<RwLock<StringList>>, pub reset_model_list: Arc<RwLock<StringList>>,
pub reset_model_index: Arc<RwLock<u32>>, pub reset_model_index: Arc<RwLock<u32>>,
pub reset_switch_initial: AtomicBool,
} }
#[glib::object_subclass] #[glib::object_subclass]

View file

@ -2,12 +2,11 @@ use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use crate::components::base::error_impl::show_error;
use crate::components::bluetooth::bluetooth_entry_impl; use crate::components::bluetooth::bluetooth_entry_impl;
use crate::components::utils::{BASE, BLUETOOTH, DBUS_PATH}; use crate::components::utils::{BASE, DBUS_PATH, BLUETOOTH};
use adw::glib::Object; use adw::glib::Object;
use adw::prelude::{ActionRowExt, PreferencesRowExt}; use adw::prelude::{ActionRowExt, PreferencesRowExt};
use adw::ActionRow; use adw::{glib, ActionRow};
use dbus::blocking::Connection; use dbus::blocking::Connection;
use dbus::{Error, Path}; use dbus::{Error, Path};
use glib::subclass::prelude::ObjectSubclassIsExt; use glib::subclass::prelude::ObjectSubclassIsExt;
@ -15,8 +14,6 @@ use gtk::prelude::{ButtonExt, ListBoxRowExt, WidgetExt};
use gtk::{gio, Align, Button, GestureClick, Image, Label}; use gtk::{gio, Align, Button, GestureClick, Image, Label};
use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice; use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice;
use super::bluetooth_box::BluetoothBox;
glib::wrapper! { glib::wrapper! {
pub struct BluetoothEntry(ObjectSubclass<bluetooth_entry_impl::BluetoothEntry>) pub struct BluetoothEntry(ObjectSubclass<bluetooth_entry_impl::BluetoothEntry>)
@extends ActionRow, gtk::Widget, @extends ActionRow, gtk::Widget,
@ -27,7 +24,7 @@ unsafe impl Send for BluetoothEntry {}
unsafe impl Sync for BluetoothEntry {} unsafe impl Sync for BluetoothEntry {}
impl BluetoothEntry { impl BluetoothEntry {
pub fn new(device: BluetoothDevice, bluetooth_box: Arc<BluetoothBox>) -> Arc<Self> { pub fn new(device: BluetoothDevice) -> Arc<Self> {
let entry: Arc<BluetoothEntry> = Arc::new(Object::builder().build()); let entry: Arc<BluetoothEntry> = Arc::new(Object::builder().build());
let entry_imp = entry.imp(); let entry_imp = entry.imp();
let entry_ref = entry.clone(); let entry_ref = entry.clone();
@ -63,14 +60,11 @@ impl BluetoothEntry {
.borrow() .borrow()
.connect_clicked(move |_| { .connect_clicked(move |_| {
let imp = entry_ref_remove.imp(); let imp = entry_ref_remove.imp();
remove_device_pairing( remove_device_pairing(imp.bluetooth_device.borrow().path.clone());
imp.bluetooth_device.borrow().path.clone(),
bluetooth_box.clone(),
);
}); });
let gesture = GestureClick::new(); let gesture = GestureClick::new();
// paired is not what we think // paired is not what we think
// FUTURE TODO: implement paired // TODO implement paired
gesture.connect_released(move |_, _, _, _| { gesture.connect_released(move |_, _, _, _| {
let imp = entry_ref.imp(); let imp = entry_ref.imp();
let borrow = imp.bluetooth_device.borrow(); let borrow = imp.bluetooth_device.borrow();
@ -93,9 +87,16 @@ impl BluetoothEntry {
fn connect_to_device(entry: Arc<BluetoothEntry>, path: Path<'static>) { fn connect_to_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap(); let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); let proxy = conn.with_proxy(
let res: Result<(bool,), Error> = BASE,
proxy.method_call(BLUETOOTH, "ConnectToBluetoothDevice", (path,)); DBUS_PATH,
Duration::from_millis(1000),
);
let res: Result<(bool,), Error> = proxy.method_call(
BLUETOOTH,
"ConnectToBluetoothDevice",
(path,),
);
glib::spawn_future(async move { glib::spawn_future(async move {
glib::idle_add_once(move || { glib::idle_add_once(move || {
if res.is_err() { if res.is_err() {
@ -133,9 +134,16 @@ fn connect_to_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
fn disconnect_from_device(entry: Arc<BluetoothEntry>, path: Path<'static>) { fn disconnect_from_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap(); let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); let proxy = conn.with_proxy(
let res: Result<(bool,), Error> = BASE,
proxy.method_call(BLUETOOTH, "DisconnectFromBluetoothDevice", (path,)); DBUS_PATH,
Duration::from_millis(1000),
);
let res: Result<(bool,), Error> = proxy.method_call(
BLUETOOTH,
"DisconnectFromBluetoothDevice",
(path,),
);
glib::spawn_future(async move { glib::spawn_future(async move {
glib::idle_add_once(move || { glib::idle_add_once(move || {
let imp = entry.imp(); let imp = entry.imp();
@ -153,14 +161,15 @@ fn disconnect_from_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
}); });
} }
fn remove_device_pairing(path: Path<'static>, wifi_box: Arc<BluetoothBox>) { fn remove_device_pairing(path: Path<'static>) {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap(); let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); let proxy = conn.with_proxy(
let res: Result<(bool,), Error> = BASE,
DBUS_PATH,
Duration::from_millis(1000),
);
let _: Result<(bool,), Error> =
proxy.method_call(BLUETOOTH, "RemoveDevicePairing", (path,)); 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::subclass::preferences_row::PreferencesRowImpl;
use adw::ActionRow; use adw::ActionRow;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, Label}; use gtk::{glib, Button, CompositeTemplate, Label};
use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice; use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice;
use std::cell::RefCell; use std::cell::RefCell;

View file

@ -1,104 +0,0 @@
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,4 +2,3 @@ pub mod bluetooth_box;
pub mod bluetooth_box_impl; pub mod bluetooth_box_impl;
pub mod bluetooth_entry; pub mod bluetooth_entry;
pub mod bluetooth_entry_impl; pub mod bluetooth_entry_impl;
mod bluetooth_event_handlers;

View file

@ -2,6 +2,5 @@ pub mod output_stream_entry;
pub mod output_stream_entry_impl; pub mod output_stream_entry_impl;
pub mod source_box; pub mod source_box;
pub mod source_box_impl; pub mod source_box_impl;
mod source_const;
pub mod source_entry; pub mod source_entry;
pub mod source_entry_impl; pub mod source_entry_impl;

View file

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

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

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

@ -5,16 +5,13 @@ use std::collections::HashMap;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::SystemTime; 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::base::list_entry::ListEntry;
use crate::components::input::source_box;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{glib, CheckButton, CompositeTemplate, StringList, TemplateChild};
use gtk::{prelude::*, Button, Label, Scale}; use gtk::{prelude::*, Button, Label, Scale};
use gtk::{CheckButton, CompositeTemplate, StringList};
use super::output_stream_entry::OutputStreamEntry; use super::output_stream_entry::OutputStreamEntry;
use super::source_const::ICONS;
use super::source_entry::SourceEntry; use super::source_entry::SourceEntry;
type SourceEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SourceEntry>, String)>>>; type SourceEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SourceEntry>, String)>>>;
@ -37,6 +34,7 @@ pub struct SourceBox {
pub reset_volume_slider: TemplateChild<Scale>, pub reset_volume_slider: TemplateChild<Scale>,
#[template_child] #[template_child]
pub reset_volume_percentage: TemplateChild<Label>, pub reset_volume_percentage: TemplateChild<Label>,
#[template_child] #[template_child]
pub reset_sources: TemplateChild<gtk::Box>, pub reset_sources: TemplateChild<gtk::Box>,
#[template_child] #[template_child]
@ -47,8 +45,6 @@ pub struct SourceBox {
pub reset_input_cards_back_button: TemplateChild<ActionRow>, pub reset_input_cards_back_button: TemplateChild<ActionRow>,
#[template_child] #[template_child]
pub reset_cards: TemplateChild<PreferencesGroup>, pub reset_cards: TemplateChild<PreferencesGroup>,
#[template_child]
pub error: TemplateChild<ReSetError>,
pub reset_default_check_button: Arc<CheckButton>, pub reset_default_check_button: Arc<CheckButton>,
pub reset_default_source: Arc<RefCell<Source>>, pub reset_default_source: Arc<RefCell<Source>>,
pub reset_source_list: SourceEntryMap, pub reset_source_list: SourceEntryMap,
@ -80,7 +76,12 @@ impl ObjectSubclass for SourceBox {
impl BoxImpl for SourceBox {} impl BoxImpl for SourceBox {}
impl ObjectImpl for SourceBox {} impl ObjectImpl for SourceBox {
fn constructed(&self) {
let obj = self.obj();
obj.setup_callbacks();
}
}
impl ListBoxRowImpl for SourceBox {} impl ListBoxRowImpl for SourceBox {}
@ -89,93 +90,3 @@ impl WidgetImpl for SourceBox {}
impl WindowImpl for SourceBox {} impl WindowImpl for SourceBox {}
impl ApplicationWindowImpl 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,163 @@
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

@ -6,11 +6,8 @@ use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{Button, CheckButton, CompositeTemplate, Label, Scale}; use gtk::{glib, 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; use super::source_entry;
#[derive(Default, CompositeTemplate)] #[derive(Default, CompositeTemplate)]
@ -51,49 +48,3 @@ impl PreferencesGroupImpl for SourceEntry {}
impl ObjectImpl for SourceEntry {} impl ObjectImpl for SourceEntry {}
impl WidgetImpl for SourceEntry {} impl WidgetImpl for SourceEntry {}
impl TAudioEntryImpl<Source> for SourceEntry {
fn name(&self) -> &TemplateChild<ActionRow> {
&self.reset_source_name
}
fn selected_audio_object(&self) -> &TemplateChild<CheckButton> {
&self.reset_selected_source
}
fn mute(&self) -> &TemplateChild<Button> {
&self.reset_source_mute
}
fn volume_slider(&self) -> &TemplateChild<Scale> {
&self.reset_volume_slider
}
fn volume_percentage(&self) -> &TemplateChild<Label> {
&self.reset_volume_percentage
}
fn audio_object(&self) -> Arc<RefCell<Source>> {
self.source.clone()
}
fn volume_time_stamp(&self) -> &RefCell<Option<SystemTime>> {
&self.volume_time_stamp
}
fn set_volume_fn(&self) -> &'static DBusFunction {
&SETVOLUME
}
fn set_audio_object_fn(&self) -> &'static DBusFunction {
&SETDEFAULT
}
fn set_mute_fn(&self) -> &'static DBusFunction {
&SETMUTE
}
fn icons(&self) -> &AudioIcons {
&ICONS
}
}

View file

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

View file

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

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

@ -2,6 +2,5 @@ pub mod input_stream_entry;
pub mod input_stream_entry_impl; pub mod input_stream_entry_impl;
pub mod sink_box; pub mod sink_box;
pub mod sink_box_impl; pub mod sink_box_impl;
mod sink_const;
pub mod sink_entry; pub mod sink_entry;
pub mod sink_entry_impl; pub mod sink_entry_impl;

View file

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

@ -5,16 +5,13 @@ use std::collections::HashMap;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::SystemTime; 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::base::list_entry::ListEntry;
use crate::components::output::input_stream_entry::InputStreamEntry;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{glib, Box, Button, CheckButton, CompositeTemplate, Label, StringList, TemplateChild};
use gtk::{prelude::*, Scale}; use gtk::{prelude::*, Scale};
use gtk::{Box, Button, CheckButton, CompositeTemplate, Label, StringList};
use super::sink_box; use super::sink_box;
use super::sink_const::ICONS;
use super::sink_entry::SinkEntry; use super::sink_entry::SinkEntry;
type SinkEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SinkEntry>, String)>>>; type SinkEntryMap = Arc<RwLock<HashMap<u32, (Arc<ListEntry>, Arc<SinkEntry>, String)>>>;
@ -48,8 +45,6 @@ pub struct SinkBox {
pub reset_input_cards_back_button: TemplateChild<ActionRow>, pub reset_input_cards_back_button: TemplateChild<ActionRow>,
#[template_child] #[template_child]
pub reset_cards: TemplateChild<PreferencesGroup>, pub reset_cards: TemplateChild<PreferencesGroup>,
#[template_child]
pub error: TemplateChild<ReSetError>,
pub reset_default_check_button: Arc<CheckButton>, pub reset_default_check_button: Arc<CheckButton>,
pub reset_default_sink: Arc<RefCell<Sink>>, pub reset_default_sink: Arc<RefCell<Sink>>,
pub reset_sink_list: SinkEntryMap, pub reset_sink_list: SinkEntryMap,
@ -81,7 +76,12 @@ impl ObjectSubclass for SinkBox {
impl BoxImpl for SinkBox {} impl BoxImpl for SinkBox {}
impl ObjectImpl for SinkBox {} impl ObjectImpl for SinkBox {
fn constructed(&self) {
let obj = self.obj();
obj.setup_callbacks();
}
}
impl ListBoxRowImpl for SinkBox {} impl ListBoxRowImpl for SinkBox {}
@ -90,93 +90,3 @@ impl WidgetImpl for SinkBox {}
impl WindowImpl for SinkBox {} impl WindowImpl for SinkBox {}
impl ApplicationWindowImpl 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,158 @@
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

@ -5,12 +5,9 @@ use std::cell::RefCell;
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use crate::components::audio::audio_entry::{AudioIcons, DBusFunction, TAudioEntryImpl}; use crate::components::output::sink_entry;
use crate::components::audio::output::sink_entry;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{Button, CheckButton, CompositeTemplate, Label, Scale}; use gtk::{glib, Button, CheckButton, CompositeTemplate, Label, Scale};
use super::sink_const::{ICONS, SETDEFAULT, SETMUTE, SETVOLUME};
#[derive(Default, CompositeTemplate)] #[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetSinkEntry.ui")] #[template(resource = "/org/Xetibo/ReSet/resetSinkEntry.ui")]
@ -25,7 +22,7 @@ pub struct SinkEntry {
pub reset_volume_slider: TemplateChild<Scale>, pub reset_volume_slider: TemplateChild<Scale>,
#[template_child] #[template_child]
pub reset_volume_percentage: TemplateChild<Label>, pub reset_volume_percentage: TemplateChild<Label>,
pub sink: Arc<RefCell<Sink>>, pub stream: Arc<RefCell<Sink>>,
pub volume_time_stamp: RefCell<Option<SystemTime>>, pub volume_time_stamp: RefCell<Option<SystemTime>>,
} }
@ -50,49 +47,3 @@ impl PreferencesGroupImpl for SinkEntry {}
impl ObjectImpl for SinkEntry {} impl ObjectImpl for SinkEntry {}
impl WidgetImpl 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,95 +0,0 @@
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

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

View file

@ -1,62 +1,30 @@
use std::cell::Cell; use adw::{ActionRow, ComboRow};
use std::time::Duration;
use adw::gdk::pango::EllipsizeMode; use adw::gdk::pango::EllipsizeMode;
use adw::prelude::ListModelExtManual; use adw::prelude::ListModelExtManual;
use adw::{ActionRow, ComboRow}; use glib::{Cast, Object};
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 gtk::{Align, SignalListItemFactory, StringObject};
use re_set_lib::ERROR; use gtk::prelude::{GObjectPropertyExpressionExt, ListBoxRowExt, ListItemExt, WidgetExt};
#[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 DBUS_PATH: &str = "/org/Xetibo/ReSet/Daemon";
pub const WIRELESS: &str = "org.Xetibo.ReSet.Network"; pub const WIRELESS: &str = "org.Xetibo.ReSet.Wireless";
pub const BLUETOOTH: &str = "org.Xetibo.ReSet.Bluetooth"; pub const BLUETOOTH: &str = "org.Xetibo.ReSet.Bluetooth";
pub const AUDIO: &str = "org.Xetibo.ReSet.Audio"; pub const AUDIO: &str = "org.Xetibo.ReSet.Audio";
pub const BASE: &str = "org.Xetibo.ReSet.Daemon"; 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 { pub fn create_dropdown_label_factory() -> SignalListItemFactory {
let factory = SignalListItemFactory::new(); let factory = SignalListItemFactory::new();
factory.connect_setup(|_, item| { factory.connect_setup(|_, item| {
let item = item.downcast_ref::<gtk::ListItem>().unwrap(); let item = item.downcast_ref::<gtk::ListItem>().unwrap();
let label = gtk::Label::new(None); let label = gtk::Label::new(None);
label.set_halign(Align::Start); label.set_halign(Align::Start);
item.property_expression("item") item.property_expression("item").chain_property::<StringObject>("string").bind(&label, "label", gtk::Widget::NONE);
.chain_property::<StringObject>("string")
.bind(&label, "label", gtk::Widget::NONE);
item.set_child(Some(&label)); item.set_child(Some(&label));
}); });
factory factory
} }
pub fn set_combo_row_ellipsis(element: ComboRow) { pub fn set_combo_row_ellipsis(element: ComboRow) {
for (i, child) in element for (i, child) in element.child().unwrap().observe_children().iter::<Object>().enumerate() {
.child()
.unwrap()
.observe_children()
.iter::<Object>()
.enumerate()
{
if i == 2 { if i == 2 {
if let Ok(object) = child { if let Ok(object) = child {
if let Some(item) = object.downcast_ref::<gtk::Box>() { if let Some(item) = object.downcast_ref::<gtk::Box>() {
@ -91,17 +59,3 @@ 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,7 +7,6 @@ pub mod wifi_box;
pub mod wifi_box_impl; pub mod wifi_box_impl;
pub mod wifi_entry; pub mod wifi_entry;
pub mod wifi_entry_impl; pub mod wifi_entry_impl;
mod wifi_event_handlers;
pub mod wifi_options; pub mod wifi_options;
pub mod wifi_options_impl; pub mod wifi_options_impl;
pub mod wifi_route_entry; 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::utils::get_connection_settings;
use crate::components::wifi::wifi_box_impl::WifiBox; use crate::components::wifi::wifi_box_impl::WifiBox;
use crate::components::wifi::wifi_options::WifiOptions; use crate::components::wifi::wifi_options::WifiOptions;
use adw::glib;
use adw::glib::Object; use adw::glib::Object;
use adw::prelude::{ActionRowExt, ButtonExt, PreferencesGroupExt, PreferencesRowExt}; use adw::prelude::{ActionRowExt, ButtonExt, PreferencesGroupExt, PreferencesRowExt};
use dbus::blocking::Connection; use dbus::blocking::Connection;
use dbus::{Error, Path}; use dbus::{Error, Path};
use glib::clone;
use glib::property::PropertySet;
use glib::subclass::types::ObjectSubclassIsExt; use glib::subclass::types::ObjectSubclassIsExt;
use glib::{clone, PropertySet};
use gtk::prelude::{BoxExt, ListBoxRowExt}; use gtk::prelude::{BoxExt, ListBoxRowExt};
use gtk::{gio, Align, Button, Orientation}; use gtk::{gio, Align, Button, Orientation};
@ -55,7 +55,7 @@ impl SavedWifiEntry {
let entry_ref = entry.clone(); let entry_ref = entry.clone();
delete_button.connect_clicked(clone!(@weak wifi_box => move |_| { delete_button.connect_clicked(clone!(@weak wifi_box => move |_| {
delete_connection(entry_ref.imp().reset_connection_path.take()); delete_connection(entry_ref.imp().reset_connection_path.take());
// FUTURE TODO: handle error // TODO handle error
wifi_box.reset_stored_wifi_list.remove(&*entry_ref); wifi_box.reset_stored_wifi_list.remove(&*entry_ref);
})); }));
@ -66,7 +66,12 @@ impl SavedWifiEntry {
fn delete_connection(path: Path<'static>) { fn delete_connection(path: Path<'static>) {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap(); let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); let proxy = conn.with_proxy(
let _: Result<(), Error> = proxy.method_call(WIRELESS, "DeleteConnection", (path,)); 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 dbus::Path;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::CompositeTemplate; use gtk::{glib, CompositeTemplate};
use super::saved_wifi_entry; use super::saved_wifi_entry;

View file

@ -22,8 +22,13 @@ type ResultType =
pub fn get_connection_settings(path: Path<'static>) -> ResetConnection { pub fn get_connection_settings(path: Path<'static>) -> ResetConnection {
let conn = Connection::new_session().unwrap(); let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); let proxy = conn.with_proxy(
let res: ResultType = proxy.method_call(WIRELESS, "GetConnectionSettings", (path,)); BASE,
DBUS_PATH,
Duration::from_millis(1000),
);
let res: ResultType =
proxy.method_call(WIRELESS, "GetConnectionSettings", (path,));
if res.is_err() { if res.is_err() {
ResetConnection::default(); ResetConnection::default();
} }

View file

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

View file

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

View file

@ -4,40 +4,28 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use crate::components::base::error_impl::{show_error, ReSetErrorImpl};
use crate::components::base::utils::Listeners; use crate::components::base::utils::Listeners;
use crate::components::utils::{set_combo_row_ellipsis, BASE, DBUS_PATH, WIRELESS}; use crate::components::utils::{set_combo_row_ellipsis, BASE, DBUS_PATH, WIRELESS};
use adw::glib;
use adw::glib::Object; use adw::glib::Object;
use adw::prelude::{ComboRowExt, ListBoxRowExt, PreferencesGroupExt}; use adw::prelude::{ComboRowExt, ListBoxRowExt, PreferencesGroupExt, PreferencesRowExt};
use adw::subclass::prelude::ObjectSubclassIsExt; use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection; use dbus::blocking::Connection;
use dbus::message::SignalArgs; use dbus::message::SignalArgs;
use dbus::Error; use dbus::Error;
use dbus::Path; use dbus::Path;
use glib::prelude::Cast; use glib::{clone, Cast, PropertySet};
use glib::property::PropertySet;
use glib::{clone, ControlFlow};
use gtk::glib::Variant; use gtk::glib::Variant;
use gtk::prelude::ActionableExt; use gtk::prelude::{ActionableExt, WidgetExt};
use gtk::{gio, StringList, StringObject}; use gtk::{gio, StringList, StringObject};
use re_set_lib::{ use re_set_lib::network::network_structures::{AccessPoint, WifiDevice, WifiStrength};
network::network_structures::{AccessPoint, WifiDevice}, use re_set_lib::signals::{AccessPointAdded, WifiDeviceChanged, WifiDeviceReset};
signals::{AccessPointAdded, WifiDeviceChanged, WifiDeviceReset}, use re_set_lib::signals::{AccessPointChanged, AccessPointRemoved};
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_box_impl;
use crate::components::wifi::wifi_entry::WifiEntry; use crate::components::wifi::wifi_entry::WifiEntry;
use super::saved_wifi_entry::SavedWifiEntry; 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! { glib::wrapper! {
pub struct WifiBox(ObjectSubclass<wifi_box_impl::WifiBox>) pub struct WifiBox(ObjectSubclass<wifi_box_impl::WifiBox>)
@ -50,14 +38,6 @@ type ResultMap = Result<(Vec<(Path<'static>, Vec<u8>)>,), Error>;
unsafe impl Send for WifiBox {} unsafe impl Send for WifiBox {}
unsafe impl Sync 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 { impl WifiBox {
pub fn new(listeners: Arc<Listeners>) -> Arc<Self> { pub fn new(listeners: Arc<Listeners>) -> Arc<Self> {
let obj: Arc<WifiBox> = Arc::new(Object::builder().build()); let obj: Arc<WifiBox> = Arc::new(Object::builder().build());
@ -70,8 +50,6 @@ impl WifiBox {
fn setup_callbacks(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) -> Arc<WifiBox> { fn setup_callbacks(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) -> Arc<WifiBox> {
let imp = wifi_box.imp(); let imp = wifi_box.imp();
let wifibox_ref = wifi_box.clone(); let wifibox_ref = wifi_box.clone();
let wifibox_ref_switch = wifi_box.clone();
imp.reset_switch_initial.set(true);
imp.reset_saved_networks.set_activatable(true); imp.reset_saved_networks.set_activatable(true);
imp.reset_saved_networks imp.reset_saved_networks
.set_action_name(Some("navigation.push")); .set_action_name(Some("navigation.push"));
@ -84,10 +62,7 @@ fn setup_callbacks(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) -> Arc<Wif
set_combo_row_ellipsis(imp.reset_wifi_device.get()); set_combo_row_ellipsis(imp.reset_wifi_device.get());
imp.reset_wifi_switch.connect_state_set( imp.reset_wifi_switch.connect_state_set(
clone!(@weak imp => @default-return glib::Propagation::Proceed, move |_, value| { clone!(@weak imp => @default-return glib::Propagation::Proceed, move |_, value| {
if imp.reset_switch_initial.load(Ordering::SeqCst) { set_wifi_enabled(value);
return glib::Propagation::Proceed;
}
set_wifi_enabled(value, wifibox_ref_switch.clone());
if !value { if !value {
imp.reset_wifi_devices.write().unwrap().clear(); imp.reset_wifi_devices.write().unwrap().clear();
*imp.reset_model_list.write().unwrap() = StringList::new(&[]); *imp.reset_model_list.write().unwrap() = StringList::new(&[]);
@ -113,21 +88,22 @@ fn setup_callbacks(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) -> Arc<Wif
pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) { pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) {
let wifibox_ref = wifi_box.clone(); 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 = wifi_box.imp().wifi_entries.clone();
let wifi_entries_path = wifi_box.imp().wifi_entries_path.clone(); let wifi_entries_path = wifi_box.imp().wifi_entries_path.clone();
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
let wifi_status = get_wifi_status(wifibox_ref.clone()); let devices = get_wifi_devices();
let devices = get_wifi_devices(wifibox_ref.clone()); let access_points = get_access_points();
if devices.is_empty() { let wifi_status = get_wifi_status();
return;
}
let access_points = get_access_points(wifi_box.clone());
{ {
let imp = wifibox_ref.imp(); let imp = wifibox_ref.imp();
let list = imp.reset_model_list.write().unwrap(); let list = imp.reset_model_list.write().unwrap();
let mut model_index = imp.reset_model_index.write().unwrap(); let mut model_index = imp.reset_model_index.write().unwrap();
let mut map = imp.reset_wifi_devices.write().unwrap(); let mut map = imp.reset_wifi_devices.write().unwrap();
if devices.is_empty() {
return;
}
imp.reset_current_wifi_device imp.reset_current_wifi_device
.replace(devices.last().unwrap().clone()); .replace(devices.last().unwrap().clone());
for (index, device) in devices.into_iter().enumerate() { for (index, device) in devices.into_iter().enumerate() {
@ -138,16 +114,15 @@ pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) {
} }
let wifi_entries = wifi_entries.clone(); let wifi_entries = wifi_entries.clone();
let wifi_entries_path = wifi_entries_path.clone(); let wifi_entries_path = wifi_entries_path.clone();
dbus_start_network_events(wifibox_ref.clone()); dbus_start_network_events();
glib::spawn_future(async move { glib::spawn_future(async move {
glib::idle_add_once(move || { glib::idle_add_once(move || {
let mut wifi_entries = wifi_entries.write().unwrap(); let mut wifi_entries = wifi_entries.write().unwrap();
let mut wifi_entries_path = wifi_entries_path.write().unwrap(); let mut wifi_entries_path = wifi_entries_path.write().unwrap();
let imp = wifibox_ref.imp(); let imp = wifibox_ref.imp();
imp.reset_wifi_switch.set_state(wifi_status);
imp.reset_wifi_switch.set_active(wifi_status); imp.reset_wifi_switch.set_active(wifi_status);
imp.reset_switch_initial.set(false); imp.reset_wifi_switch.set_state(wifi_status);
let list = imp.reset_model_list.read().unwrap(); let list = imp.reset_model_list.read().unwrap();
imp.reset_wifi_device.set_model(Some(&*list)); imp.reset_wifi_device.set_model(Some(&*list));
@ -159,11 +134,24 @@ pub fn scan_for_wifi(wifi_box: Arc<WifiBox>) {
} }
} }
let device_changed_ref = wifibox_ref.clone(); imp.reset_wifi_device.connect_selected_notify(
imp.reset_wifi_device clone!(@weak imp => move |dropdown| {
.connect_selected_notify(move |dropdown| { let selected = dropdown.selected_item();
select_wifi_device_handler(dropdown, device_changed_ref.clone()); 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());
}),
);
for access_point in access_points { for access_point in access_points {
if access_point.ssid.is_empty() { if access_point.ssid.is_empty() {
continue; continue;
@ -182,34 +170,15 @@ 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>) { pub fn show_stored_connections(wifi_box: Arc<WifiBox>) {
let wifibox_ref = wifi_box.clone(); let wifibox_ref = wifi_box.clone();
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
let connections = get_stored_connections(wifi_box.clone()); let connections = get_stored_connections();
glib::spawn_future(async move { glib::spawn_future(async move {
glib::idle_add_once(move || { glib::idle_add_once(move || {
let self_imp = wifibox_ref.imp(); let self_imp = wifibox_ref.imp();
for connection in connections { for connection in connections {
// FUTURE TODO: include button for settings // TODO include button for settings
let name = let name =
&String::from_utf8(connection.1).unwrap_or_else(|_| String::from("")); &String::from_utf8(connection.1).unwrap_or_else(|_| String::from(""));
let entry = SavedWifiEntry::new(name, connection.0, self_imp); let entry = SavedWifiEntry::new(name, connection.0, self_imp);
@ -220,80 +189,67 @@ pub fn show_stored_connections(wifi_box: Arc<WifiBox>) {
}); });
} }
pub fn dbus_start_network_events(wifi_box: Arc<WifiBox>) { pub fn dbus_start_network_events() {
let conn = Connection::new_session().unwrap(); 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 res: Result<(), Error> = proxy.method_call(WIRELESS, "StartNetworkListener", ()); let _: Result<(), Error> = proxy.method_call(WIRELESS, "StartNetworkListener", ());
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to start Network listener");
}
} }
pub fn get_access_points(wifi_box: Arc<WifiBox>) -> Vec<AccessPoint> { pub fn get_access_points() -> Vec<AccessPoint> {
let conn = Connection::new_session().unwrap(); 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 res: Result<(Vec<AccessPoint>,), Error> = let res: Result<(Vec<AccessPoint>,), Error> =
proxy.method_call(WIRELESS, "ListAccessPoints", ()); proxy.method_call(WIRELESS, "ListAccessPoints", ());
if res.is_err() { if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to list access points");
return Vec::new(); return Vec::new();
} }
let (access_points,) = res.unwrap(); let (access_points,) = res.unwrap();
access_points access_points
} }
pub fn set_wifi_device(path: Path<'static>, wifi_box: Arc<WifiBox>) { pub fn set_wifi_device(path: Path<'static>) {
let conn = Connection::new_session().unwrap(); 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 res: Result<(bool,), Error> = proxy.method_call(WIRELESS, "SetWifiDevice", (path,)); let _: Result<(bool,), Error> = proxy.method_call(WIRELESS, "SetWifiDevice", (path,));
if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to set WiFi devices");
}
} }
pub fn get_wifi_devices(wifi_box: Arc<WifiBox>) -> Vec<WifiDevice> { pub fn get_wifi_devices() -> Vec<WifiDevice> {
let conn = Connection::new_session().unwrap(); 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 res: Result<(Vec<WifiDevice>,), Error> = let res: Result<(Vec<WifiDevice>,), Error> =
proxy.method_call(WIRELESS, "GetAllWifiDevices", ()); proxy.method_call(WIRELESS, "GetAllWifiDevices", ());
if res.is_err() { if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to get WiFi devices");
return Vec::new(); return Vec::new();
} }
let (devices,) = res.unwrap(); let (devices,) = res.unwrap();
devices devices
} }
pub fn get_wifi_status(wifi_box: Arc<WifiBox>) -> bool { pub fn get_wifi_status() -> bool {
let conn = Connection::new_session().unwrap(); 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 res: Result<(bool,), Error> = proxy.method_call(WIRELESS, "GetWifiStatus", ()); let res: Result<(bool,), Error> = proxy.method_call(WIRELESS, "GetWifiStatus", ());
if res.is_err() { if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to get WiFi status");
return false; return false;
} }
res.unwrap().0 res.unwrap().0
} }
pub fn get_stored_connections(wifi_box: Arc<WifiBox>) -> Vec<(Path<'static>, Vec<u8>)> { pub fn get_stored_connections() -> Vec<(Path<'static>, Vec<u8>)> {
let conn = Connection::new_session().unwrap(); 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 res: ResultMap = proxy.method_call(WIRELESS, "ListStoredConnections", ()); let res: ResultMap = proxy.method_call(WIRELESS, "ListStoredConnections", ());
if res.is_err() { if res.is_err() {
show_error::<WifiBox>(wifi_box.clone(), "Failed to list stored connections");
return Vec::new(); return Vec::new();
} }
let (connections,) = res.unwrap(); let (connections,) = res.unwrap();
connections connections
} }
pub fn set_wifi_enabled(enabled: bool, wifi_box: Arc<WifiBox>) { pub fn set_wifi_enabled(enabled: bool) {
let conn = Connection::new_session().unwrap(); 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 res: Result<(bool,), Error> = proxy.method_call(WIRELESS, "SetWifiEnabled", (enabled,)); let _: 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>) { pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
@ -327,53 +283,179 @@ pub fn start_event_listener(listeners: Arc<Listeners>, wifi_box: Arc<WifiBox>) {
WifiDeviceReset::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH))) WifiDeviceReset::match_rule(Some(&BASE.into()), Some(&Path::from(DBUS_PATH)))
.static_clone(); .static_clone();
let res = conn.add_match(access_point_added, move |ir: AccessPointAdded, _, _| { let res = conn.add_match(access_point_added, move |ir: AccessPointAdded, _, _| {
access_point_added_handler(added_ref.clone(), ir) 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
}); });
if res.is_err() { if res.is_err() {
ERROR!( println!("fail on access point add event");
"fail on access point add event",
ErrorLevel::PartialBreakage
);
return; return;
} }
let res = conn.add_match(access_point_removed, move |ir: AccessPointRemoved, _, _| { let res = conn.add_match(access_point_removed, move |ir: AccessPointRemoved, _, _| {
access_point_removed_handler(removed_ref.clone(), ir) 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
}); });
if res.is_err() { if res.is_err() {
ERROR!( println!("fail on access point remove event");
"fail on access point remove event",
ErrorLevel::PartialBreakage
);
return; return;
} }
let res = conn.add_match(access_point_changed, move |ir: AccessPointChanged, _, _| { let res = conn.add_match(access_point_changed, move |ir: AccessPointChanged, _, _| {
access_point_changed_handler(changed_ref.clone(), ir) 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
}); });
if res.is_err() { if res.is_err() {
ERROR!( println!("fail on access point change event");
"fail on access point change event",
ErrorLevel::PartialBreakage
);
return; return;
} }
let res = conn.add_match(device_changed, move |ir: WifiDeviceChanged, _, _| { let res = conn.add_match(device_changed, move |ir: WifiDeviceChanged, _, _| {
wifi_device_changed_handler(wifi_changed_ref.clone(), ir) 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
}); });
if res.is_err() { if res.is_err() {
ERROR!( println!("fail on wifi device change event");
"fail on wifi device change event",
ErrorLevel::PartialBreakage
);
return; return;
} }
let res = conn.add_match(devices_reset, move |ir: WifiDeviceReset, _, _| { let res = conn.add_match(devices_reset, move |ir: WifiDeviceReset, _, _| {
wifi_device_reset_handler(wifi_reset_ref.clone(), ir) 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
}); });
if res.is_err() { if res.is_err() {
ERROR!( println!("fail on wifi device change event");
"fail on wifi device change event",
ErrorLevel::PartialBreakage
);
return; return;
} }

View file

@ -1,14 +1,12 @@
use crate::components::base::error::ReSetError;
use crate::components::wifi::wifi_box; use crate::components::wifi::wifi_box;
use adw::{ActionRow, ComboRow, NavigationView, PreferencesGroup}; use adw::{ActionRow, ComboRow, NavigationView, PreferencesGroup};
use dbus::Path; use dbus::Path;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{glib, CompositeTemplate, Switch};
use gtk::{prelude::*, StringList}; use gtk::{prelude::*, StringList};
use gtk::{CompositeTemplate, Switch};
use re_set_lib::network::network_structures::WifiDevice; use re_set_lib::network::network_structures::WifiDevice;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crate::components::base::list_entry::ListEntry; use crate::components::base::list_entry::ListEntry;
@ -33,15 +31,12 @@ pub struct WifiBox {
pub reset_stored_wifi_list: TemplateChild<PreferencesGroup>, pub reset_stored_wifi_list: TemplateChild<PreferencesGroup>,
#[template_child] #[template_child]
pub reset_available_networks: TemplateChild<ActionRow>, 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: Arc<RwLock<HashMap<Vec<u8>, Arc<WifiEntry>>>>,
pub wifi_entries_path: Arc<RwLock<HashMap<Path<'static>, Arc<WifiEntry>>>>, pub wifi_entries_path: Arc<RwLock<HashMap<Path<'static>, Arc<WifiEntry>>>>,
pub reset_wifi_devices: Arc<RwLock<HashMap<String, (WifiDevice, u32)>>>, pub reset_wifi_devices: Arc<RwLock<HashMap<String, (WifiDevice, u32)>>>,
pub reset_current_wifi_device: Arc<RefCell<WifiDevice>>, pub reset_current_wifi_device: Arc<RefCell<WifiDevice>>,
pub reset_model_list: Arc<RwLock<StringList>>, pub reset_model_list: Arc<RwLock<StringList>>,
pub reset_model_index: Arc<RwLock<u32>>, pub reset_model_index: Arc<RwLock<u32>>,
pub reset_switch_initial: AtomicBool,
} }
unsafe impl Send for WifiBox {} unsafe impl Send for WifiBox {}

View file

@ -4,13 +4,13 @@ use std::time::Duration;
use crate::components::utils::{BASE, DBUS_PATH, WIRELESS}; use crate::components::utils::{BASE, DBUS_PATH, WIRELESS};
use crate::components::wifi::utils::get_connection_settings; use crate::components::wifi::utils::get_connection_settings;
use adw::glib::Object; use adw::glib;
use adw::glib::{Object, PropertySet};
use adw::prelude::{ActionRowExt, ButtonExt, EditableExt, PopoverExt, PreferencesRowExt}; use adw::prelude::{ActionRowExt, ButtonExt, EditableExt, PopoverExt, PreferencesRowExt};
use adw::subclass::prelude::ObjectSubclassIsExt; use adw::subclass::prelude::ObjectSubclassIsExt;
use dbus::blocking::Connection; use dbus::blocking::Connection;
use dbus::Error; use dbus::Error;
use glib::clone; use glib::clone;
use glib::property::PropertySet;
use gtk::prelude::{BoxExt, ListBoxRowExt, WidgetExt}; use gtk::prelude::{BoxExt, ListBoxRowExt, WidgetExt};
use gtk::{gio, Align, Button, Image, Orientation}; use gtk::{gio, Align, Button, Image, Orientation};
use re_set_lib::network::network_structures::{AccessPoint, WifiStrength}; use re_set_lib::network::network_structures::{AccessPoint, WifiStrength};
@ -48,7 +48,7 @@ impl WifiEntry {
.build(), .build(),
); );
// FUTURE TODO: handle encryption // TODO handle encryption thing
let wifi_strength = Image::builder() let wifi_strength = Image::builder()
.icon_name(match strength { .icon_name(match strength {
WifiStrength::Excellent => "network-wireless-signal-excellent-symbolic", WifiStrength::Excellent => "network-wireless-signal-excellent-symbolic",
@ -177,7 +177,7 @@ pub fn click_stored_network(entry: Arc<WifiEntry>) {
}); });
}); });
}); });
// FUTURE TODO: crate spinner animation and block UI // TODO crate spinner animation and block UI
} }
pub fn click_new_network(entry: Arc<WifiEntry>) { pub fn click_new_network(entry: Arc<WifiEntry>) {
@ -228,7 +228,7 @@ pub fn click_new_network(entry: Arc<WifiEntry>) {
}); });
}); });
}); });
// FUTURE TODO: crate spinner animation and block UI // TODO crate spinner animation and block UI
}; };
let entry_imp = entry.imp(); let entry_imp = entry.imp();

View file

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

View file

@ -1,166 +0,0 @@
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,14 +4,13 @@ use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use adw::gio;
use adw::glib::Object; use adw::glib::Object;
use adw::prelude::{ActionRowExt, ComboRowExt, PreferencesGroupExt}; use adw::prelude::{ActionRowExt, ComboRowExt, PreferencesGroupExt};
use adw::subclass::prelude::ObjectSubclassIsExt; use adw::subclass::prelude::ObjectSubclassIsExt;
use adw::{gio, glib};
use dbus::arg::PropMap; use dbus::arg::PropMap;
use dbus::{Error, Path}; use dbus::{Error, Path};
use glib::clone; use glib::{clone, PropertySet};
use glib::property::PropertySet;
use gtk::prelude::{ActionableExt, ButtonExt, EditableExt, ListBoxRowExt, WidgetExt}; use gtk::prelude::{ActionableExt, ButtonExt, EditableExt, ListBoxRowExt, WidgetExt};
use re_set_lib::network::connection::{ use re_set_lib::network::connection::{
Connection, DNSMethod4, DNSMethod6, Enum, KeyManagement, TypeSettings, Connection, DNSMethod4, DNSMethod6, Enum, KeyManagement, TypeSettings,
@ -390,8 +389,15 @@ fn setup_callbacks(wifi_options: &Arc<WifiOptions>, path: Path<'static>) {
fn set_connection_settings(path: Path<'static>, prop: HashMap<String, PropMap>) { fn set_connection_settings(path: Path<'static>, prop: HashMap<String, PropMap>) {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
let conn = dbus::blocking::Connection::new_session().unwrap(); let conn = dbus::blocking::Connection::new_session().unwrap();
let proxy = conn.with_proxy(BASE, DBUS_PATH, Duration::from_millis(1000)); let proxy = conn.with_proxy(
let _: Result<(bool,), Error> = BASE,
proxy.method_call(WIRELESS, "SetConnectionSettings", (path, prop)); 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, ActionRow, ComboRow, EntryRow, NavigationPage, PasswordEntryRow, PreferencesGroup, SwitchRow,
}; };
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, Label}; use gtk::{glib, Button, CompositeTemplate, Label};
use re_set_lib::network::connection::Connection; use re_set_lib::network::connection::Connection;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;

View file

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

View file

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

View file

@ -1,48 +0,0 @@
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,103 +5,70 @@ use std::rc::Rc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; 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::setting_box::SettingBox;
use crate::components::base::utils::{start_audio_listener, Listeners, Position}; use crate::components::base::utils::{start_audio_listener, Listeners, Position};
use crate::components::bluetooth::bluetooth_box::{ use crate::components::bluetooth::bluetooth_box::{
populate_connected_bluetooth_devices, BluetoothBox, populate_conntected_bluetooth_devices, start_bluetooth_listener, BluetoothBox,
}; };
use crate::components::utils::Capabilities; use crate::components::input::source_box::{populate_sources, SourceBox};
use crate::components::output::sink_box::{populate_sinks, SinkBox};
use crate::components::wifi::wifi_box::{ use crate::components::wifi::wifi_box::{
scan_for_wifi, show_stored_connections, start_event_listener, WifiBox, scan_for_wifi, show_stored_connections, start_event_listener, WifiBox,
}; };
use gtk::prelude::WidgetExt; use gtk::prelude::WidgetExt;
use gtk::{Align, FlowBox, FlowBoxChild, Frame}; use gtk::{Align, FlowBox, FlowBoxChild, Frame};
pub const HANDLE_CONNECTIVITY_CLICK: fn( pub const HANDLE_CONNECTIVITY_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
&Capabilities, |listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
Arc<Listeners>, if handle_init(listeners.clone(), position, Position::Connectivity) {
FlowBox, return;
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()); let wifi_box = WifiBox::new(listeners.clone());
start_event_listener(listeners.clone(), wifi_box.clone()); start_event_listener(listeners.clone(), wifi_box.clone());
show_stored_connections(wifi_box.clone()); show_stored_connections(wifi_box.clone());
scan_for_wifi(wifi_box.clone()); scan_for_wifi(wifi_box.clone());
let wifi_frame = wrap_in_flow_box_child(SettingBox::new(&*wifi_box)); let wifi_frame = wrap_in_flow_box_child(SettingBox::new(&*wifi_box));
reset_main.insert(&wifi_frame, -1);
count += 1;
}
if capabilities.bluetooth.get() {
let bluetooth_box = BluetoothBox::new(listeners.clone()); let bluetooth_box = BluetoothBox::new(listeners.clone());
populate_connected_bluetooth_devices(listeners, bluetooth_box.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)); 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.insert(&bluetooth_frame, -1);
count += 1; reset_main.set_max_children_per_line(2);
} };
reset_main.set_max_children_per_line(count); pub const HANDLE_WIFI_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
}; |listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
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) { if handle_init(listeners.clone(), position, Position::Wifi) {
return; return;
} }
reset_main.remove_all();
let wifi_box = WifiBox::new(listeners.clone()); let wifi_box = WifiBox::new(listeners.clone());
start_event_listener(listeners, wifi_box.clone()); start_event_listener(listeners, wifi_box.clone());
show_stored_connections(wifi_box.clone()); show_stored_connections(wifi_box.clone());
scan_for_wifi(wifi_box.clone()); scan_for_wifi(wifi_box.clone());
let wifi_frame = wrap_in_flow_box_child(SettingBox::new(&*wifi_box)); let wifi_frame = wrap_in_flow_box_child(SettingBox::new(&*wifi_box));
reset_main.remove_all();
reset_main.insert(&wifi_frame, -1); reset_main.insert(&wifi_frame, -1);
reset_main.set_max_children_per_line(1); reset_main.set_max_children_per_line(1);
}; };
pub const HANDLE_BLUETOOTH_CLICK: fn( pub const HANDLE_BLUETOOTH_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
&Capabilities, |listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
Arc<Listeners>, if handle_init(listeners.clone(), position, Position::Bluetooth) {
FlowBox, return;
Rc<RefCell<Position>>, }
) = |_: &Capabilities, let bluetooth_box = BluetoothBox::new(listeners.clone());
listeners: Arc<Listeners>, start_bluetooth_listener(listeners, bluetooth_box.clone());
reset_main: FlowBox, populate_conntected_bluetooth_devices(bluetooth_box.clone());
position: Rc<RefCell<Position>>| { let bluetooth_frame = wrap_in_flow_box_child(SettingBox::new(&*bluetooth_box));
if handle_init(listeners.clone(), position, Position::Bluetooth) { reset_main.remove_all();
return; reset_main.insert(&bluetooth_frame, -1);
} reset_main.set_max_children_per_line(1);
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(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) = pub const HANDLE_AUDIO_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|_: &Capabilities, |listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::Audio) { if handle_init(listeners.clone(), position, Position::Audio) {
return; return;
} }
@ -125,11 +92,8 @@ pub const HANDLE_AUDIO_CLICK: fn(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefC
reset_main.set_max_children_per_line(2); reset_main.set_max_children_per_line(2);
}; };
pub const HANDLE_VOLUME_CLICK: fn(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) = pub const HANDLE_VOLUME_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|_: &Capabilities, |listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners.clone(), position, Position::AudioOutput) { if handle_init(listeners.clone(), position, Position::AudioOutput) {
return; return;
} }
@ -145,35 +109,25 @@ pub const HANDLE_VOLUME_CLICK: fn(&Capabilities, Arc<Listeners>, FlowBox, Rc<Ref
reset_main.set_max_children_per_line(1); reset_main.set_max_children_per_line(1);
}; };
pub const HANDLE_MICROPHONE_CLICK: fn( pub const HANDLE_MICROPHONE_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
&Capabilities, |listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
Arc<Listeners>, if handle_init(listeners.clone(), position, Position::AudioInput) {
FlowBox, return;
Rc<RefCell<Position>>, }
) = |_: &Capabilities, let audio_input = Arc::new(SourceBox::new());
listeners: Arc<Listeners>, start_audio_listener(listeners.clone(), None, Some(audio_input.clone()));
reset_main: FlowBox, if !listeners.pulse_listener.load(Ordering::SeqCst) {
position: Rc<RefCell<Position>>| { spin_loop();
if handle_init(listeners.clone(), position, Position::AudioInput) { }
return; populate_sources(audio_input.clone());
} let source_frame = wrap_in_flow_box_child(SettingBox::new(&*audio_input));
let audio_input = Arc::new(SourceBox::new()); reset_main.remove_all();
start_audio_listener(listeners.clone(), None, Some(audio_input.clone())); reset_main.insert(&source_frame, -1);
if !listeners.pulse_listener.load(Ordering::SeqCst) { reset_main.set_max_children_per_line(1);
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(&Capabilities, Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) = pub const HANDLE_HOME: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|_: &Capabilities, |listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
listeners: Arc<Listeners>,
reset_main: FlowBox,
position: Rc<RefCell<Position>>| {
if handle_init(listeners, position, Position::Home) { if handle_init(listeners, position, Position::Home) {
return; return;
} }
@ -208,3 +162,60 @@ fn handle_init(
listeners.stop_bluetooth_listener(); listeners.stop_bluetooth_listener();
false 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,4 +3,3 @@ pub mod reset_window;
pub mod reset_window_impl; pub mod reset_window_impl;
pub mod sidebar_entry; pub mod sidebar_entry;
pub mod sidebar_entry_impl; pub mod sidebar_entry_impl;
pub mod consts;

View file

@ -1,32 +1,15 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use adw::glib::clone; use adw::glib::clone;
use adw::subclass::prelude::ObjectSubclassIsExt; use adw::subclass::prelude::ObjectSubclassIsExt;
use adw::BreakpointCondition; use adw::BreakpointCondition;
use glib::Object; use glib::Object;
use gtk::gio::ActionEntry; use gtk::gio::ActionEntry;
use gtk::{ use gtk::{gio, glib, AccessibleRole, Application, ListBoxRow, Orientation};
gio, AccessibleRole, Align, Application, FlowBox, FlowBoxChild, Frame, ListBoxRow, Orientation,
StateFlags,
};
use gtk::{prelude::*, DirectionType}; 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::handle_sidebar_click::*;
use crate::components::window::reset_window_impl; use crate::components::window::reset_window_impl;
use crate::components::window::sidebar_entry::SidebarEntry; use crate::components::window::sidebar_entry::SidebarEntry;
use crate::VERSION; use crate::components::window::sidebar_entry_impl::Categories;
use super::consts::{
AUDIO_SIDEBAR, BLUETOOTH_SIDEBAR, CONNECTIVITY_SIDEBAR, SINK_SIDEBAR, SOURCE_SIDEBAR,
WIFI_SIDEBAR,
};
glib::wrapper! { glib::wrapper! {
pub struct ReSetWindow(ObjectSubclass<reset_window_impl::ReSetWindow>) pub struct ReSetWindow(ObjectSubclass<reset_window_impl::ReSetWindow>)
@ -40,7 +23,7 @@ unsafe impl Send for ReSetWindow {}
unsafe impl Sync for ReSetWindow {} unsafe impl Sync for ReSetWindow {}
impl ReSetWindow { impl ReSetWindow {
pub fn new(app: &Application) -> Rc<Self> { pub fn new(app: &Application) -> Self {
app.set_accels_for_action("win.search", &["<Ctrl>F"]); app.set_accels_for_action("win.search", &["<Ctrl>F"]);
app.set_accels_for_action("win.close", &["<Ctrl>Q"]); app.set_accels_for_action("win.close", &["<Ctrl>Q"]);
app.set_accels_for_action("win.about", &["<Ctrl>H"]); app.set_accels_for_action("win.about", &["<Ctrl>H"]);
@ -49,9 +32,111 @@ impl ReSetWindow {
// app.set_accels_for_action("win.right", &["<Ctrl>L"]); // app.set_accels_for_action("win.right", &["<Ctrl>L"]);
// app.set_accels_for_action("win.down", &["<Ctrl>J"]); // app.set_accels_for_action("win.down", &["<Ctrl>J"]);
// app.set_accels_for_action("win.left", &["<Ctrl>H"]); // app.set_accels_for_action("win.left", &["<Ctrl>H"]);
let mut window: Rc<Self> = Rc::new(Object::builder().property("application", app).build()); Object::builder().property("application", app).build()
window = setup_callback(window); }
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 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.0.1")
.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,
]);
}
pub fn setup_callback(&self) {
let self_imp = self.imp();
self_imp.reset_search_entry.connect_search_changed(
clone!(@ weak self as window => move |_| {
window.filter_list();
}),
);
self_imp
.reset_sidebar_toggle
.connect_clicked(clone!(@ weak self as window => move |_| {
window.toggle_sidebar();
}));
self_imp.reset_sidebar_list.connect_row_activated(
clone!(@ weak self_imp as flowbox => move |_, y| {
let result = y.downcast_ref::<SidebarEntry>().unwrap();
let click_event = result.imp().on_click_event.borrow().on_click_event;
(click_event)(flowbox.listeners.clone(), flowbox.reset_main.get(), flowbox.position.clone());
}),
);
self_imp
.reset_close
.connect_clicked(clone!(@ weak self as window => move |_| {
window.close();
}));
} }
pub fn handle_dynamic_sidebar(&self) { pub fn handle_dynamic_sidebar(&self) {
@ -119,84 +204,108 @@ impl ReSetWindow {
pub fn setup_sidebar_entries(&self) { pub fn setup_sidebar_entries(&self) {
let self_imp = self.imp(); let self_imp = self.imp();
let capabilities = get_capabilities(); let mut sidebar_entries = self_imp.sidebar_entries.borrow_mut();
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 mut sidebar_list = Vec::new(); let connectivity_list = vec![
SidebarEntry::new(
"WiFi",
"network-wireless-symbolic",
Categories::Connectivity,
true,
HANDLE_WIFI_CLICK,
),
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,
// ),
];
if wifi || bluetooth { sidebar_entries.push((
sidebar_list.push(CONNECTIVITY_SIDEBAR); SidebarEntry::new(
} "Connectivity",
if wifi { "network-wired-symbolic",
sidebar_list.push(WIFI_SIDEBAR); Categories::Connectivity,
}; false,
if bluetooth { HANDLE_CONNECTIVITY_CLICK,
sidebar_list.push(BLUETOOTH_SIDEBAR); ),
}; connectivity_list,
if audio { ));
sidebar_list.push(AUDIO_SIDEBAR);
sidebar_list.push(SINK_SIDEBAR);
sidebar_list.push(SOURCE_SIDEBAR);
}
let mut plugin_sidebar_list = vec![]; let audio_list = vec![
unsafe { SidebarEntry::new(
for plugin in FRONTEND_PLUGINS.iter() { "Output",
let plugin_capabilities = &plugin.capabilities; "audio-volume-high-symbolic",
Categories::Audio,
true,
HANDLE_VOLUME_CLICK,
),
SidebarEntry::new(
"Input",
"audio-input-microphone-symbolic",
Categories::Audio,
true,
HANDLE_MICROPHONE_CLICK,
),
];
(plugin.frontend_startup)(); sidebar_entries.push((
SidebarEntry::new(
"Audio",
"audio-headset-symbolic",
Categories::Audio,
false,
HANDLE_AUDIO_CLICK,
),
audio_list,
));
let (sidebar_info, plugin_boxes) = (plugin.frontend_data)(); // uncommented when implemented
let listeners = self_imp.listeners.clone(); // 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,
// ),
// ];
if plugin_capabilities.1 { // let home = SidebarEntry::new(
let mut found = false; // "Home",
for capability in plugin_capabilities.0.iter() { // "preferences-system-devices-symbolic",
if capabilities.contains(&capability.to_string()) { // Categories::Peripherals,
found = true; // false,
break; // HANDLE_VOLUME_CLICK,
} // );
} //
if !found { // sidebar_entries.push((home, Vec::new()));
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);
},
);
plugin_sidebar_list.push(PluginSidebarInfo { (HANDLE_VOLUME_CLICK)(
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.listeners.clone(),
self_imp.reset_main.clone(), self_imp.reset_main.clone(),
self_imp.position.clone(), self_imp.position.clone(),
@ -207,246 +316,29 @@ impl ReSetWindow {
.connect_row_activated(clone!(@ weak self_imp => move |_, _| { .connect_row_activated(clone!(@ weak self_imp => move |_, _| {
self_imp.reset_search_entry.set_text(""); self_imp.reset_search_entry.set_text("");
})); }));
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 entry = SidebarEntry::new(&info);
self_imp.reset_sidebar_list.insert(&entry, i);
i += 1;
}
for info in plugin_sidebar_list { for (main_entry, sub_entries) in sidebar_entries.iter() {
if info.parent.is_none() && i != 0 { self_imp.reset_sidebar_list.append(main_entry);
self_imp.reset_sidebar_list.insert(&create_separator(), i); for sub_entry in sub_entries {
i += 1; self_imp.reset_sidebar_list.append(sub_entry);
} }
let entry = SidebarEntry::new(&info); let separator = gtk::Separator::builder()
self_imp.reset_sidebar_list.insert(&entry, i); .margin_bottom(3)
i += 1; .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);
} }
} }
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();
let search_ref = window.clone();
let toggle_ref = window.clone();
let close_ref = window.clone();
self_imp
.reset_search_entry
.connect_search_changed(move |_| {
search_ref.filter_list();
});
self_imp.reset_sidebar_toggle.connect_clicked(move |_| {
toggle_ref.toggle_sidebar();
});
self_imp
.reset_sidebar_list
.connect_row_activated(move |_, y| {
let imp = activated_ref.imp();
let result = y.downcast_ref::<SidebarEntry>().unwrap();
{
let mut default_entry = imp.default_entry.borrow_mut();
if default_entry.is_some() {
default_entry
.clone()
.unwrap()
.set_state_flags(StateFlags::NORMAL, true);
*default_entry = None;
}
}
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,31 +2,25 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use adw::glib::StaticTypeExt;
use adw::subclass::prelude::AdwApplicationWindowImpl; use adw::subclass::prelude::AdwApplicationWindowImpl;
use adw::{Breakpoint, OverlaySplitView}; use adw::{Breakpoint, OverlaySplitView};
use glib::prelude::StaticTypeExt;
use glib::subclass::InitializingObject; use glib::subclass::InitializingObject;
use gtk::prelude::WidgetExt; use gtk::prelude::WidgetExt;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{Button, CompositeTemplate, FlowBox, ListBox, SearchEntry}; use gtk::{glib, Button, CompositeTemplate, FlowBox, ListBox, SearchEntry};
use crate::components::base::error::ReSetError;
use crate::components::base::utils::{Listeners, Position}; use crate::components::base::utils::{Listeners, Position};
use crate::components::utils::Capabilities;
use crate::components::wifi::wifi_box::WifiBox; use crate::components::wifi::wifi_box::WifiBox;
use crate::components::window::reset_window; use crate::components::window::reset_window;
use crate::components::window::sidebar_entry::SidebarEntry; use crate::components::window::sidebar_entry::SidebarEntry;
type SidebarEntries = RefCell<Vec<(Rc<SidebarEntry>, Vec<Rc<SidebarEntry>>)>>;
#[derive(CompositeTemplate, Default)] #[derive(CompositeTemplate, Default)]
#[template(resource = "/org/Xetibo/ReSet/resetMainWindow.ui")] #[template(resource = "/org/Xetibo/ReSet/resetMainWindow.ui")]
pub struct ReSetWindow { pub struct ReSetWindow {
#[template_child] #[template_child]
pub reset_main: TemplateChild<FlowBox>, pub reset_main: TemplateChild<FlowBox>,
#[template_child] #[template_child]
pub reset_banner: TemplateChild<adw::Banner>,
#[template_child]
pub reset_sidebar_breakpoint: TemplateChild<Breakpoint>, pub reset_sidebar_breakpoint: TemplateChild<Breakpoint>,
#[template_child] #[template_child]
pub reset_overlay_split_view: TemplateChild<OverlaySplitView>, pub reset_overlay_split_view: TemplateChild<OverlaySplitView>,
@ -38,14 +32,9 @@ pub struct ReSetWindow {
pub reset_sidebar_toggle: TemplateChild<Button>, pub reset_sidebar_toggle: TemplateChild<Button>,
#[template_child] #[template_child]
pub reset_close: TemplateChild<Button>, pub reset_close: TemplateChild<Button>,
// #[template_child] pub sidebar_entries: RefCell<Vec<(SidebarEntry, Vec<SidebarEntry>)>>,
// pub reset_banner: TemplateChild<adw::Banner>,
pub sidebar_entries: SidebarEntries,
pub default_entry: RefCell<Option<Rc<SidebarEntry>>>,
pub listeners: Arc<Listeners>, pub listeners: Arc<Listeners>,
pub position: Rc<RefCell<Position>>, pub position: Rc<RefCell<Position>>,
pub error_popup: ReSetError,
pub capabilities: Capabilities,
} }
unsafe impl Send for ReSetWindow {} unsafe impl Send for ReSetWindow {}
@ -74,6 +63,7 @@ impl ObjectImpl for ReSetWindow {
let obj = self.obj(); let obj = self.obj();
obj.setup_shortcuts(); obj.setup_shortcuts();
obj.setup_callback();
obj.handle_dynamic_sidebar(); obj.handle_dynamic_sidebar();
obj.setup_sidebar_entries(); obj.setup_sidebar_entries();
} }
@ -85,20 +75,14 @@ impl WidgetImpl for ReSetWindow {
if width > 658 { if width > 658 {
self.reset_main.set_margin_start(60); self.reset_main.set_margin_start(60);
self.reset_main.set_margin_end(60); self.reset_main.set_margin_end(60);
self.reset_banner.set_margin_start(60);
self.reset_banner.set_margin_end(60);
} else { } else {
let div = (width - 540) / 2; let div = (width - 540) / 2;
if div > 1 { if div > 1 {
self.reset_main.set_margin_start(div); self.reset_main.set_margin_start(div);
self.reset_main.set_margin_end(div); self.reset_main.set_margin_end(div);
self.reset_banner.set_margin_start(div);
self.reset_banner.set_margin_end(div);
} else { } else {
self.reset_main.set_margin_start(0); self.reset_main.set_margin_start(0);
self.reset_main.set_margin_end(0); self.reset_main.set_margin_end(0);
self.reset_banner.set_margin_start(0);
self.reset_banner.set_margin_end(0);
} }
} }
} }

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