Compare commits

..

No commits in common. "main" and "0-1" have entirely different histories.
main ... 0-1

145 changed files with 2355 additions and 16757 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}}-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,17 @@ 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"
- name: Build Ubuntu package
run: | run: |
mkdir -p ./debian/usr cd flatpak
mkdir -p ./debian/usr/bin python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json
mkdir -p ./debian/usr/share flatpak-builder build org.xetibo.ReSet.json --force-clean
mkdir -p ./debian/usr/share/applications flatpak build-export export build
mkdir -p ./debian/usr/share/pixmaps flatpak build-bundle export reset.flatpak org.xetibo.ReSet
cp ./target/release/ReSet ./debian/usr/bin/ReSet
cp ./ReSet.desktop ./debian/usr/share/applications/.
cp ./src/resources/icons/ReSet.svg ./debian/usr/share/pixmaps/.
dpkg-deb --build debian
mv debian.deb ReSet.deb
- 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

View file

@ -1,33 +1,37 @@
# 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
permissions: runs-on: [self-hosted, ubuntu]
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: run code coverage
- name: Set up cache uses: actions-rs/tarpaulin@v0.1
uses: DeterminateSystems/magic-nix-cache-action@main
- name: action
uses: cachix/install-nix-action@v25
with: with:
nix_path: nixpkgs=channel:nixos-unstable version: '0.15.0'
- name: cache args: '-- --test-threads 1'
uses: cachix/cachix-action@v14 - name: upload code coverage
uses: actions/upload-artifact@v1
with: with:
name: reset name: code-coverage-report
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' path: cobertura.xml
- name: Build - name: Build
run: nix -L build github:${{ github.repository }}/${{ github.sha }} --no-write-lock-file run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

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,32 +1,13 @@
[package] [package]
name = "reset" name = "reset"
version = "2.0.0" version = "0.1.0"
edition = "2021" edition = "2021"
description = "A wip universal Linux settings application." description = "A wip universal Linux settings application."
repository = "https://github.com/Xetibo/ReSet"
license = "GPL-3.0-or-later"
[[bin]]
name = "ReSet"
path = "src/main.rs"
[dependencies] [dependencies]
reset_daemon = "2.2.0" adw = { version = "0.5.3", package = "libadwaita", features = ["v1_4"]}
re_set-lib = "5.2.5"
adw = { version = "0.6.0", 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"
tokio = { version = "1.36.0", features = [
"rt",
"time",
"net",
"macros",
"rt-multi-thread",
"sync",
] }
fork = "0.1.23"
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
pkgrel=0 pkgrel=1
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 +0,0 @@
Package: ReSet
Version: 2.0.0
Maintainer: DashieTM
Architecture: all
Description: A wip universal Linux settings application.
Homepage: https://github.com/Xetibo/ReSet
Build-Depends: rust
Depends: libadwaita-1-0, libgtk-4-1, dbus
Recommends: pipewire-pulse, network-manager, bluez

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

@ -2,10 +2,10 @@
Name=ReSet 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;
Icon=org.Xetibo.ReSet Icon=org.xetibo.ReSet
Categories=Utility;GTK; Categories=Utility;GTK;
StartupNotify=false StartupNotify=false

View file

@ -1,27 +1,19 @@
{ {
"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-stable"],
"org.freedesktop.Sdk.Extension.rust-nightly" "command": "reset",
],
"command": "ReSet",
"finish-args": [ "finish-args": [
"--socket=pulseaudio", "--socket=session-bus",
"--share=network",
"--share=ipc", "--share=ipc",
"--socket=fallback-x11", "--socket=fallback-x11",
"--socket=wayland", "--socket=wayland",
"--device=dri", "--device=dri"
"--device=all",
"--allow=bluetooth",
"--socket=system-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,9 +27,9 @@
"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"
], ],
"sources": [ "sources": [
{ {

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

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

View file

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

View file

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

View file

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

View file

@ -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,7 +0,0 @@
pub mod output_stream_entry;
pub mod output_stream_entry_impl;
pub mod source_box;
pub mod source_box_impl;
mod source_const;
pub mod source_entry;
pub mod source_entry_impl;

View file

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

View file

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

View file

@ -1,8 +1,5 @@
mod audio_box_handlers; #![allow(non_snake_case)]
mod audio_box_utils; pub mod audioSource;
mod audio_const; pub mod audioBox;
pub mod audio_entry; pub mod audioBoxImpl;
pub mod audio_functions; pub mod audioSourceImpl;
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,7 +0,0 @@
pub mod input_stream_entry;
pub mod input_stream_entry_impl;
pub mod sink_box;
pub mod sink_box_impl;
mod sink_const;
pub mod sink_entry;
pub mod sink_entry_impl;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

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