mirror of
https://github.com/Xetibo/ReSet.git
synced 2025-04-08 22:52:01 +02:00
commit
6263ac59bf
21
.github/workflows/release-arch.yml
vendored
Normal file
21
.github/workflows/release-arch.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
on:
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: [self-hosted, arch]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: nightly-rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
- name: Build rust package
|
||||
run: makepkg PKGBUILD
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
reset-${{github.ref_name}}-0-x86_64.pkg.tar.zst
|
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
|
@ -1,21 +1,35 @@
|
|||
on:
|
||||
release:
|
||||
types: [created]
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, ubuntu]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: nightly-rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
- name: Build
|
||||
- name: Build rust package
|
||||
run: cargo build --release --verbose
|
||||
- name: Build Flatpak
|
||||
run: |
|
||||
cd flatpak
|
||||
python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json
|
||||
flatpak-builder build org.xetibo.ReSet.json --force-clean
|
||||
flatpak build-export export build
|
||||
flatpak build-bundle export reset.flatpak org.xetibo.ReSet
|
||||
- name: Build Ubuntu package
|
||||
run: |
|
||||
cp ./target/release/reset ./debian/.
|
||||
dpkg-deb --build debian
|
||||
mv debian.deb reset.deb
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
target/release/reset
|
||||
flatpak/reset.flatpak
|
||||
reset.deb
|
||||
|
|
49
.github/workflows/rust.yml
vendored
49
.github/workflows/rust.yml
vendored
|
@ -2,6 +2,7 @@ name: Rust
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
|
@ -11,32 +12,26 @@ env:
|
|||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, ubuntu]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: nightly-rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: target/debug
|
||||
key: ${{ runner.os }}-cache
|
||||
- name: run code coverage
|
||||
uses: actions-rs/tarpaulin@v0.1
|
||||
with:
|
||||
version: '0.15.0'
|
||||
args: '-- --test-threads 1'
|
||||
- name: upload code coverage
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: code-coverage-report
|
||||
path: cobertura.xml
|
||||
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
- uses: actions/checkout@v3
|
||||
- name: nightly-rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
- name: run code coverage
|
||||
uses: actions-rs/tarpaulin@v0.1
|
||||
with:
|
||||
version: '0.15.0'
|
||||
args: '-- --test-threads 1'
|
||||
- name: upload code coverage
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: code-coverage-report
|
||||
path: cobertura.xml
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -2,6 +2,12 @@
|
|||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
flatpak/build
|
||||
flatpak/.flatpak-builder
|
||||
flatpak/export
|
||||
flatpak/reset.flatpak
|
||||
pkg/
|
||||
*.pkg.tar.zst
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
|
@ -17,4 +23,4 @@ Cargo.lock
|
|||
# Added by cargo
|
||||
|
||||
/target
|
||||
.idea/
|
||||
.idea/
|
||||
|
|
19
Cargo.toml
19
Cargo.toml
|
@ -5,9 +5,22 @@ edition = "2021"
|
|||
description = "A wip universal Linux settings application."
|
||||
|
||||
[dependencies]
|
||||
ReSet-Lib = "0.1.0"
|
||||
adw = { version = "0.5.3", package = "libadwaita" }
|
||||
gtk = { version = "0.7.3", package = "gtk4" }
|
||||
reset_daemon = "0.4.6"
|
||||
re_set-lib = "0.6.5"
|
||||
adw = { version = "0.5.3", package = "libadwaita", features = ["v1_4"] }
|
||||
dbus = "0.9.7"
|
||||
gtk = { version = "0.7.3", package = "gtk4", features = ["v4_12"] }
|
||||
glib = "0.18.3"
|
||||
tokio = { version = "1.33.0", features = [
|
||||
"rt",
|
||||
"time",
|
||||
"net",
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"sync",
|
||||
] }
|
||||
fork = "0.1.22"
|
||||
ipnetwork = "0.20.0"
|
||||
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.18.0"
|
||||
|
|
20
PKGBUILD
Normal file
20
PKGBUILD
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Maintainer: Fabio Lenherr <dashie@dashie.org>
|
||||
|
||||
pkgname=reset
|
||||
pkgver=0.1
|
||||
pkgrel=0
|
||||
arch=('x86_64')
|
||||
pkgdir="/usr/bin/${pkgname}"
|
||||
pkgdesc="A wip universal Linux settings application."
|
||||
depends=('rust' 'gtk4' 'dbus')
|
||||
|
||||
build() {
|
||||
cargo build --release
|
||||
}
|
||||
|
||||
package() {
|
||||
cd ..
|
||||
install -Dm755 target/release/"$pkgname" "$pkgdir"/usr/bin/"$pkgname"
|
||||
install -Dm644 "$pkgname.desktop" "$pkgdir/usr/share/applications/$pkgname.desktop"
|
||||
install -Dm644 "src/resources/icons/ReSet.svg" "$pkgdir/usr/share/pixmaps/ReSet.svg"
|
||||
}
|
12
build.rs
12
build.rs
|
@ -4,4 +4,14 @@ fn main() {
|
|||
"src/resources/resources.gresource.xml",
|
||||
"src.templates.gresource",
|
||||
);
|
||||
}
|
||||
glib_build_tools::compile_resources(
|
||||
&["src/resources/icons"],
|
||||
"src/resources/icons/resources.gresource.xml",
|
||||
"src.icons.gresource",
|
||||
);
|
||||
glib_build_tools::compile_resources(
|
||||
&["src/resources/style"],
|
||||
"src/resources/style/resources.gresource.xml",
|
||||
"src.style.gresource",
|
||||
);
|
||||
}
|
||||
|
|
5
debian/DEBIAN/control
vendored
Normal file
5
debian/DEBIAN/control
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
Package: ReSet
|
||||
Version: 0.1
|
||||
Maintainer: DashieTM
|
||||
Architecture: all
|
||||
Description: A wip universal Linux settings application.
|
17
flatpak/README.md
Normal file
17
flatpak/README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
### instructions for building:
|
||||
|
||||
- `python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json`
|
||||
- `flatpak-builder build org.xetibo.ReSet.json --force-clean`
|
||||
- `flatpak build-export export build`
|
||||
- `flatpak build-bundle export reset.flatpak org.xetibo.ReSet`
|
||||
- you can also use the build.sh script provided
|
||||
- note: if you are using a point release distribution(ubuntu, debian stable etc. please use the flatpak version of these commands -> flatpak run org.flatpak.Builder build...)
|
||||
|
||||
### instructions for installation:
|
||||
|
||||
`flatpak install --user reset.flatpak`
|
||||
|
||||
### permissions
|
||||
currently ReSet uses permission on all devices, for some reason otherwise it can't access sound settings like volume changes etc.
|
||||
|
||||
This can likely be fixed by implementing portal integration later.
|
5
flatpak/build.sh
Executable file
5
flatpak/build.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#! /bin/bash
|
||||
python3 flatpak-generator.py ../Cargo.lock -o cargo-sources.json
|
||||
flatpak-builder build org.xetibo.ReSet.json --force-clean
|
||||
flatpak build-export export build
|
||||
flatpak build-bundle export reset.flatpak org.xetibo.ReSet
|
1581
flatpak/cargo-sources.json
Normal file
1581
flatpak/cargo-sources.json
Normal file
File diff suppressed because it is too large
Load diff
381
flatpak/flatpak-generator.py
Normal file
381
flatpak/flatpak-generator.py
Normal file
|
@ -0,0 +1,381 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# provided by flatpak -> flatpak-builder-tools
|
||||
# https://github.com/flatpak/flatpak-builder-tools/tree/master/cargo
|
||||
__license__ = 'MIT'
|
||||
import json
|
||||
from urllib.parse import urlparse, ParseResult, parse_qs
|
||||
import os
|
||||
import contextlib
|
||||
import glob
|
||||
import subprocess
|
||||
import argparse
|
||||
import logging
|
||||
import hashlib
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import toml
|
||||
|
||||
CRATES_IO = 'https://static.crates.io/crates'
|
||||
CARGO_HOME = 'cargo'
|
||||
CARGO_CRATES = f'{CARGO_HOME}/vendor'
|
||||
VENDORED_SOURCES = 'vendored-sources'
|
||||
GIT_CACHE = 'flatpak-cargo/git'
|
||||
COMMIT_LEN = 7
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def workdir(path: str):
|
||||
oldpath = os.getcwd()
|
||||
os.chdir(path)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(oldpath)
|
||||
|
||||
|
||||
def canonical_url(url):
|
||||
'Converts a string to a Cargo Canonical URL, as per https://github.com/rust-lang/cargo/blob/35c55a93200c84a4de4627f1770f76a8ad268a39/src/cargo/util/canonical_url.rs#L19'
|
||||
# Hrm. The upstream cargo does not replace those URLs, but if we don't then it doesn't work too well :(
|
||||
url = url.replace('git+https://', 'https://')
|
||||
u = urlparse(url)
|
||||
# It seems cargo drops query and fragment
|
||||
u = ParseResult(u.scheme, u.netloc, u.path, None, None, None)
|
||||
u = u._replace(path = u.path.rstrip('/'))
|
||||
|
||||
if u.netloc == 'github.com':
|
||||
u = u._replace(scheme = 'https')
|
||||
u = u._replace(path = u.path.lower())
|
||||
|
||||
if u.path.endswith('.git'):
|
||||
u = u._replace(path = u.path[:-len('.git')])
|
||||
|
||||
return u
|
||||
|
||||
|
||||
def get_git_tarball(repo_url, commit):
|
||||
url = canonical_url(repo_url)
|
||||
path = url.path.split('/')[1:]
|
||||
|
||||
assert len(path) == 2
|
||||
owner = path[0]
|
||||
if path[1].endswith('.git'):
|
||||
repo = path[1].replace('.git', '')
|
||||
else:
|
||||
repo = path[1]
|
||||
if url.hostname == 'github.com':
|
||||
return f'https://codeload.{url.hostname}/{owner}/{repo}/tar.gz/{commit}'
|
||||
elif url.hostname.split('.')[0] == 'gitlab':
|
||||
return f'https://{url.hostname}/{owner}/{repo}/-/archive/{commit}/{repo}-{commit}.tar.gz'
|
||||
elif url.hostname == 'bitbucket.org':
|
||||
return f'https://{url.hostname}/{owner}/{repo}/get/{commit}.tar.gz'
|
||||
else:
|
||||
raise ValueError(f'Don\'t know how to get tarball for {repo_url}')
|
||||
|
||||
|
||||
async def get_remote_sha256(url):
|
||||
logging.info(f"started sha256({url})")
|
||||
sha256 = hashlib.sha256()
|
||||
async with aiohttp.ClientSession(raise_for_status=True) as http_session:
|
||||
async with http_session.get(url) as response:
|
||||
while True:
|
||||
data = await response.content.read(4096)
|
||||
if not data:
|
||||
break
|
||||
sha256.update(data)
|
||||
logging.info(f"done sha256({url})")
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
def load_toml(tomlfile='Cargo.lock'):
|
||||
with open(tomlfile, 'r') as f:
|
||||
toml_data = toml.load(f)
|
||||
return toml_data
|
||||
|
||||
|
||||
def git_repo_name(git_url, commit):
|
||||
name = canonical_url(git_url).path.split('/')[-1]
|
||||
return f'{name}-{commit[:COMMIT_LEN]}'
|
||||
|
||||
|
||||
def fetch_git_repo(git_url, commit):
|
||||
repo_dir = git_url.replace('://', '_').replace('/', '_')
|
||||
cache_dir = os.environ.get('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
|
||||
clone_dir = os.path.join(cache_dir, 'flatpak-cargo', repo_dir)
|
||||
if not os.path.isdir(os.path.join(clone_dir, '.git')):
|
||||
subprocess.run(['git', 'clone', '--depth=1', git_url, clone_dir], check=True)
|
||||
rev_parse_proc = subprocess.run(['git', 'rev-parse', 'HEAD'], cwd=clone_dir, check=True,
|
||||
stdout=subprocess.PIPE)
|
||||
head = rev_parse_proc.stdout.decode().strip()
|
||||
if head[:COMMIT_LEN] != commit[:COMMIT_LEN]:
|
||||
subprocess.run(['git', 'fetch', 'origin', commit], cwd=clone_dir, check=True)
|
||||
subprocess.run(['git', 'checkout', commit], cwd=clone_dir, check=True)
|
||||
return clone_dir
|
||||
|
||||
|
||||
async def get_git_repo_packages(git_url, commit):
|
||||
logging.info('Loading packages from %s', git_url)
|
||||
git_repo_dir = fetch_git_repo(git_url, commit)
|
||||
packages = {}
|
||||
|
||||
with workdir(git_repo_dir):
|
||||
if os.path.isfile('Cargo.toml'):
|
||||
packages.update(await get_cargo_toml_packages(load_toml('Cargo.toml'), '.'))
|
||||
else:
|
||||
for toml_path in glob.glob('*/Cargo.toml'):
|
||||
packages.update(await get_cargo_toml_packages(load_toml(toml_path),
|
||||
os.path.dirname(toml_path)))
|
||||
|
||||
assert packages, f"No packages found in {git_repo_dir}"
|
||||
logging.debug('Packages in %s:\n%s', git_url, json.dumps(packages, indent=4))
|
||||
return packages
|
||||
|
||||
|
||||
async def get_cargo_toml_packages(root_toml, root_dir):
|
||||
assert not os.path.isabs(root_dir) and os.path.isdir(root_dir)
|
||||
assert 'package' in root_toml or 'workspace' in root_toml
|
||||
packages = {}
|
||||
excluded_paths = None
|
||||
|
||||
def is_excluded(path):
|
||||
if not excluded_paths:
|
||||
return False
|
||||
return any(os.path.commonpath([excluded_path, path]) == excluded_path
|
||||
for excluded_path in excluded_paths)
|
||||
|
||||
async def get_dep_packages(entry, toml_dir):
|
||||
assert not os.path.isabs(toml_dir)
|
||||
# https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
|
||||
if 'dependencies' in entry:
|
||||
for dep_name, dep in entry['dependencies'].items():
|
||||
if 'package' in dep:
|
||||
dep_name = dep['package']
|
||||
if 'path' not in dep:
|
||||
continue
|
||||
if dep_name in packages:
|
||||
continue
|
||||
dep_dir = os.path.normpath(os.path.join(toml_dir, dep['path']))
|
||||
if is_excluded(dep_dir):
|
||||
logging.warning("Excluded dependency at %s", dep_dir)
|
||||
return
|
||||
logging.debug("Loading dependency %s from %s", dep_name, dep_dir)
|
||||
dep_toml = load_toml(os.path.join(dep_dir, 'Cargo.toml'))
|
||||
assert dep_toml['package']['name'] == dep_name, toml_dir
|
||||
await get_dep_packages(dep_toml, dep_dir)
|
||||
packages[dep_name] = dep_dir
|
||||
if 'target' in entry:
|
||||
for _, target in entry['target'].items():
|
||||
await get_dep_packages(target, toml_dir)
|
||||
|
||||
if 'package' in root_toml:
|
||||
await get_dep_packages(root_toml, root_dir)
|
||||
packages[root_toml['package']['name']] = root_dir
|
||||
|
||||
if 'workspace' in root_toml:
|
||||
workspace = root_toml['workspace']
|
||||
if 'exclude' in workspace:
|
||||
excluded_paths = [os.path.normpath(os.path.join(root_dir, excluded))
|
||||
for excluded in workspace['exclude']]
|
||||
for member in workspace.get('members', []):
|
||||
for subpkg_toml in glob.glob(os.path.join(root_dir, member, 'Cargo.toml')):
|
||||
subpkg = os.path.normpath(os.path.dirname(subpkg_toml))
|
||||
if is_excluded(subpkg):
|
||||
logging.warning("Excluded member at %s", subpkg)
|
||||
continue
|
||||
logging.debug("Loading workspace member %s in %s", member, root_dir)
|
||||
pkg_toml = load_toml(subpkg_toml)
|
||||
await get_dep_packages(pkg_toml, subpkg)
|
||||
packages[pkg_toml['package']['name']] = subpkg
|
||||
|
||||
return packages
|
||||
|
||||
|
||||
async def get_git_repo_sources(url, commit, tarball=False):
|
||||
name = git_repo_name(url, commit)
|
||||
if tarball:
|
||||
tarball_url = get_git_tarball(url, commit)
|
||||
git_repo_sources = [{
|
||||
'type': 'archive',
|
||||
'archive-type': 'tar-gzip',
|
||||
'url': tarball_url,
|
||||
'sha256': await get_remote_sha256(tarball_url),
|
||||
'dest': f'{GIT_CACHE}/{name}',
|
||||
}]
|
||||
else:
|
||||
git_repo_sources = [{
|
||||
'type': 'git',
|
||||
'url': url,
|
||||
'commit': commit,
|
||||
'dest': f'{GIT_CACHE}/{name}',
|
||||
}]
|
||||
return git_repo_sources
|
||||
|
||||
|
||||
async def get_git_package_sources(package, git_repos):
|
||||
name = package['name']
|
||||
source = package['source']
|
||||
commit = urlparse(source).fragment
|
||||
assert commit, 'The commit needs to be indicated in the fragement part'
|
||||
canonical = canonical_url(source)
|
||||
repo_url = canonical.geturl()
|
||||
|
||||
git_repo = git_repos.setdefault(repo_url, {
|
||||
'commits': {},
|
||||
'lock': asyncio.Lock(),
|
||||
})
|
||||
async with git_repo['lock']:
|
||||
if commit not in git_repo['commits']:
|
||||
git_repo['commits'][commit] = await get_git_repo_packages(repo_url, commit)
|
||||
|
||||
cargo_vendored_entry = {
|
||||
repo_url: {
|
||||
'git': repo_url,
|
||||
'replace-with': VENDORED_SOURCES,
|
||||
}
|
||||
}
|
||||
rev = parse_qs(urlparse(source).query).get('rev')
|
||||
tag = parse_qs(urlparse(source).query).get('tag')
|
||||
branch = parse_qs(urlparse(source).query).get('branch')
|
||||
if rev:
|
||||
assert len(rev) == 1
|
||||
cargo_vendored_entry[repo_url]['rev'] = rev[0]
|
||||
elif tag:
|
||||
assert len(tag) == 1
|
||||
cargo_vendored_entry[repo_url]['tag'] = tag[0]
|
||||
elif branch:
|
||||
assert len(branch) == 1
|
||||
cargo_vendored_entry[repo_url]['branch'] = branch[0]
|
||||
|
||||
logging.info("Adding package %s from %s", name, repo_url)
|
||||
pkg_subpath = git_repo['commits'][commit][name]
|
||||
pkg_repo_dir = os.path.join(GIT_CACHE, git_repo_name(repo_url, commit), pkg_subpath)
|
||||
git_sources = [
|
||||
{
|
||||
'type': 'shell',
|
||||
'commands': [
|
||||
f'cp -r --reflink=auto "{pkg_repo_dir}" "{CARGO_CRATES}/{name}"'
|
||||
],
|
||||
},
|
||||
{
|
||||
'type': 'inline',
|
||||
'contents': json.dumps({'package': None, 'files': {}}),
|
||||
'dest': f'{CARGO_CRATES}/{name}', #-{version}',
|
||||
'dest-filename': '.cargo-checksum.json',
|
||||
}
|
||||
]
|
||||
|
||||
return (git_sources, cargo_vendored_entry)
|
||||
|
||||
|
||||
async def get_package_sources(package, cargo_lock, git_repos):
|
||||
metadata = cargo_lock.get('metadata')
|
||||
name = package['name']
|
||||
version = package['version']
|
||||
|
||||
if 'source' not in package:
|
||||
logging.debug('%s has no source', name)
|
||||
return
|
||||
source = package['source']
|
||||
|
||||
if source.startswith('git+'):
|
||||
return await get_git_package_sources(package, git_repos)
|
||||
|
||||
key = f'checksum {name} {version} ({source})'
|
||||
if metadata is not None and key in metadata:
|
||||
checksum = metadata[key]
|
||||
elif 'checksum' in package:
|
||||
checksum = package['checksum']
|
||||
else:
|
||||
logging.warning(f'{name} doesn\'t have checksum')
|
||||
return
|
||||
crate_sources = [
|
||||
{
|
||||
'type': 'archive',
|
||||
'archive-type': 'tar-gzip',
|
||||
'url': f'{CRATES_IO}/{name}/{name}-{version}.crate',
|
||||
'sha256': checksum,
|
||||
'dest': f'{CARGO_CRATES}/{name}-{version}',
|
||||
},
|
||||
{
|
||||
'type': 'inline',
|
||||
'contents': json.dumps({'package': checksum, 'files': {}}),
|
||||
'dest': f'{CARGO_CRATES}/{name}-{version}',
|
||||
'dest-filename': '.cargo-checksum.json',
|
||||
},
|
||||
]
|
||||
return (crate_sources, {'crates-io': {'replace-with': VENDORED_SOURCES}})
|
||||
|
||||
|
||||
async def generate_sources(cargo_lock, git_tarballs=False):
|
||||
# {
|
||||
# "git-repo-url": {
|
||||
# "lock": asyncio.Lock(),
|
||||
# "commits": {
|
||||
# "commit-hash": {
|
||||
# "package-name": "./relative/package/path"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
git_repos = {}
|
||||
sources = []
|
||||
package_sources = []
|
||||
cargo_vendored_sources = {
|
||||
VENDORED_SOURCES: {'directory': f'{CARGO_CRATES}'},
|
||||
}
|
||||
|
||||
pkg_coros = [get_package_sources(p, cargo_lock, git_repos) for p in cargo_lock['package']]
|
||||
for pkg in await asyncio.gather(*pkg_coros):
|
||||
if pkg is None:
|
||||
continue
|
||||
else:
|
||||
pkg_sources, cargo_vendored_entry = pkg
|
||||
package_sources.extend(pkg_sources)
|
||||
cargo_vendored_sources.update(cargo_vendored_entry)
|
||||
|
||||
logging.debug('Adding collected git repos:\n%s', json.dumps(list(git_repos), indent=4))
|
||||
git_repo_coros = []
|
||||
for git_url, git_repo in git_repos.items():
|
||||
for git_commit in git_repo['commits']:
|
||||
git_repo_coros.append(get_git_repo_sources(git_url, git_commit, git_tarballs))
|
||||
sources.extend(sum(await asyncio.gather(*git_repo_coros), []))
|
||||
|
||||
sources.extend(package_sources)
|
||||
|
||||
logging.debug('Vendored sources:\n%s', json.dumps(cargo_vendored_sources, indent=4))
|
||||
sources.append({
|
||||
'type': 'inline',
|
||||
'contents': toml.dumps({
|
||||
'source': cargo_vendored_sources,
|
||||
}),
|
||||
'dest': CARGO_HOME,
|
||||
'dest-filename': 'config'
|
||||
})
|
||||
return sources
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('cargo_lock', help='Path to the Cargo.lock file')
|
||||
parser.add_argument('-o', '--output', required=False, help='Where to write generated sources')
|
||||
parser.add_argument('-t', '--git-tarballs', action='store_true', help='Download git repos as tarballs')
|
||||
parser.add_argument('-d', '--debug', action='store_true')
|
||||
args = parser.parse_args()
|
||||
if args.output is not None:
|
||||
outfile = args.output
|
||||
else:
|
||||
outfile = 'generated-sources.json'
|
||||
if args.debug:
|
||||
loglevel = logging.DEBUG
|
||||
else:
|
||||
loglevel = logging.INFO
|
||||
logging.basicConfig(level=loglevel)
|
||||
|
||||
generated_sources = asyncio.run(generate_sources(load_toml(args.cargo_lock),
|
||||
git_tarballs=args.git_tarballs))
|
||||
with open(outfile, 'w') as out:
|
||||
json.dump(generated_sources, out, indent=4, sort_keys=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
11
flatpak/org.xetibo.ReSet.desktop
Normal file
11
flatpak/org.xetibo.ReSet.desktop
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Name=ReSet
|
||||
GenericName=SettingsApplication
|
||||
GenericName[de]=SettingsApplikation
|
||||
Exec=reset
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Keywords=settings;gtk;
|
||||
Icon=org.xetibo.ReSet
|
||||
Categories=Utility;GTK;
|
||||
StartupNotify=false
|
49
flatpak/org.xetibo.ReSet.json
Normal file
49
flatpak/org.xetibo.ReSet.json
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"app-id": "org.xetibo.ReSet",
|
||||
"runtime": "org.gnome.Platform",
|
||||
"runtime-version": "45",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
"sdk-extensions": [
|
||||
"org.freedesktop.Sdk.Extension.rust-stable"
|
||||
],
|
||||
"command": "reset",
|
||||
"finish-args": [
|
||||
"--socket=session-bus",
|
||||
"--socket=system-bus",
|
||||
"--socket=pulseaudio",
|
||||
"--share=ipc",
|
||||
"--socket=fallback-x11",
|
||||
"--socket=wayland",
|
||||
"--device=dri",
|
||||
"--device=all",
|
||||
"--allow=bluetooth"
|
||||
],
|
||||
"build-options": {
|
||||
"append-path": "/usr/lib/sdk/rust-stable/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 --release --verbose",
|
||||
"install -Dm755 ./target/release/reset -t /app/bin/",
|
||||
"install -Dm644 ./src/resources/icons/ReSet.svg /app/share/icons/hicolor/scalable/apps/org.xetibo.ReSet.svg",
|
||||
"install -Dm644 ./flatpak/org.xetibo.ReSet.desktop /app/share/applications/org.xetibo.ReSet.desktop"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "dir",
|
||||
"path": ".."
|
||||
},
|
||||
"cargo-sources.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
11
reset.desktop
Normal file
11
reset.desktop
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Name=ReSet
|
||||
GenericName=SettingsApplication
|
||||
GenericName[de]=SettingsApplikation
|
||||
Exec=reset
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Keywords=settings;gtk;
|
||||
Icon=ReSet
|
||||
Categories=Utility;GTK;
|
||||
StartupNotify=false
|
80
src/components/base/card_entry.rs
Normal file
80
src/components/base/card_entry.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ComboRowExt, PreferencesRowExt};
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::Error;
|
||||
use glib::subclass::types::ObjectSubclassIsExt;
|
||||
use glib::{clone, Cast};
|
||||
use gtk::{gio, StringList, StringObject};
|
||||
|
||||
use components::utils::create_dropdown_label_factory;
|
||||
use re_set_lib::audio::audio_structures::Card;
|
||||
|
||||
use crate::components;
|
||||
|
||||
use 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(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetCardProfileOfDevice",
|
||||
(device_index, profile_name),
|
||||
);
|
||||
});
|
||||
true
|
||||
}
|
53
src/components/base/card_entry_impl.rs
Normal file
53
src/components/base/card_entry_impl.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use adw::subclass::action_row::ActionRowImpl;
|
||||
use adw::subclass::preferences_row::PreferencesRowImpl;
|
||||
use adw::subclass::prelude::ComboRowImpl;
|
||||
use adw::ComboRow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, 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 {}
|
22
src/components/base/list_entry.rs
Normal file
22
src/components/base/list_entry.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use crate::components::base::list_entry_impl;
|
||||
use adw::glib;
|
||||
use adw::glib::{IsA, Object};
|
||||
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
|
||||
}
|
||||
}
|
37
src/components/base/list_entry_impl.rs
Normal file
37
src/components/base/list_entry_impl.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use crate::components::base::list_entry;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, 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 {}
|
9
src/components/base/mod.rs
Normal file
9
src/components/base/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
pub mod card_entry;
|
||||
pub mod card_entry_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;
|
24
src/components/base/popup.rs
Normal file
24
src/components/base/popup.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use adw::glib;
|
||||
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()
|
||||
}
|
||||
}
|
54
src/components/base/popup_impl.rs
Normal file
54
src/components/base/popup_impl.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, 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 {}
|
19
src/components/base/setting_box.rs
Normal file
19
src/components/base/setting_box.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use crate::components::base::setting_box_impl;
|
||||
use adw::glib;
|
||||
use adw::glib::{IsA, Object};
|
||||
use gtk::prelude::BoxExt;
|
||||
use gtk::Widget;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct SettingBox(ObjectSubclass<setting_box_impl::SettingBox>)
|
||||
@extends gtk::Box, gtk::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
|
||||
}
|
||||
}
|
37
src/components/base/setting_box_impl.rs
Normal file
37
src/components/base/setting_box_impl.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use crate::components::base::setting_box;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, 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 {}
|
449
src/components/base/utils.rs
Normal file
449
src/components/base/utils.rs
Normal file
|
@ -0,0 +1,449 @@
|
|||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use dbus::{
|
||||
arg::{self, Append},
|
||||
blocking::Connection,
|
||||
Error,
|
||||
};
|
||||
use gtk::gio;
|
||||
use re_set_lib::{
|
||||
audio::audio_structures::{InputStream, OutputStream, Sink, Source},
|
||||
signals::GetVal,
|
||||
};
|
||||
|
||||
use crate::components::{
|
||||
input::source_box::{start_input_box_listener, SourceBox},
|
||||
output::sink_box::{start_output_box_listener, SinkBox},
|
||||
};
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
pub enum Position {
|
||||
Connectivity,
|
||||
Wifi,
|
||||
Bluetooth,
|
||||
Audio,
|
||||
AudioOutput,
|
||||
AudioInput,
|
||||
#[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(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(bool,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "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);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SinkAdded {
|
||||
pub sink: Sink,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for SinkAdded {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.sink.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for SinkAdded {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(SinkAdded { sink: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for SinkAdded {
|
||||
const NAME: &'static str = "SinkAdded";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(Sink,)> for SinkAdded {
|
||||
fn get_value(&self) -> (Sink,) {
|
||||
(self.sink.clone(),)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SinkChanged {
|
||||
pub sink: Sink,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for SinkChanged {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.sink.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for SinkChanged {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(SinkChanged { sink: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for SinkChanged {
|
||||
const NAME: &'static str = "SinkChanged";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(Sink,)> for SinkChanged {
|
||||
fn get_value(&self) -> (Sink,) {
|
||||
(self.sink.clone(),)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SinkRemoved {
|
||||
pub index: u32,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for SinkRemoved {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.index.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for SinkRemoved {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(SinkRemoved { index: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for SinkRemoved {
|
||||
const NAME: &'static str = "SinkRemoved";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(u32,)> for SinkRemoved {
|
||||
fn get_value(&self) -> (u32,) {
|
||||
(self.index,)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputStreamAdded {
|
||||
pub stream: InputStream,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for InputStreamAdded {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.stream.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for InputStreamAdded {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(InputStreamAdded { stream: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for InputStreamAdded {
|
||||
const NAME: &'static str = "InputStreamAdded";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(InputStream,)> for InputStreamAdded {
|
||||
fn get_value(&self) -> (InputStream,) {
|
||||
(self.stream.clone(),)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputStreamChanged {
|
||||
pub stream: InputStream,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for InputStreamChanged {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.stream.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for InputStreamChanged {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(InputStreamChanged { stream: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for InputStreamChanged {
|
||||
const NAME: &'static str = "InputStreamChanged";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputStreamRemoved {
|
||||
pub index: u32,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for InputStreamRemoved {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.index.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for InputStreamRemoved {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(InputStreamRemoved { index: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for InputStreamRemoved {
|
||||
const NAME: &'static str = "InputStreamRemoved";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(u32,)> for InputStreamRemoved {
|
||||
fn get_value(&self) -> (u32,) {
|
||||
(self.index,)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SourceAdded {
|
||||
pub source: Source,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for SourceAdded {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.source.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for SourceAdded {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(SourceAdded { source: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for SourceAdded {
|
||||
const NAME: &'static str = "SourceAdded";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(Source,)> for SourceAdded {
|
||||
fn get_value(&self) -> (Source,) {
|
||||
(self.source.clone(),)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SourceChanged {
|
||||
pub source: Source,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for SourceChanged {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.source.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for SourceChanged {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(SourceChanged { source: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for SourceChanged {
|
||||
const NAME: &'static str = "SourceChanged";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(Source,)> for SourceChanged {
|
||||
fn get_value(&self) -> (Source,) {
|
||||
(self.source.clone(),)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SourceRemoved {
|
||||
pub index: u32,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for SourceRemoved {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.index.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for SourceRemoved {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(SourceRemoved { index: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for SourceRemoved {
|
||||
const NAME: &'static str = "SourceRemoved";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(u32,)> for SourceRemoved {
|
||||
fn get_value(&self) -> (u32,) {
|
||||
(self.index,)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OutputStreamAdded {
|
||||
pub stream: OutputStream,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for OutputStreamAdded {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.stream.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for OutputStreamAdded {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(OutputStreamAdded { stream: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for OutputStreamAdded {
|
||||
const NAME: &'static str = "OutputStreamAdded";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(OutputStream,)> for OutputStreamAdded {
|
||||
fn get_value(&self) -> (OutputStream,) {
|
||||
(self.stream.clone(),)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OutputStreamChanged {
|
||||
pub stream: OutputStream,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for OutputStreamChanged {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.stream.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for OutputStreamChanged {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(OutputStreamChanged { stream: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for OutputStreamChanged {
|
||||
const NAME: &'static str = "OutputStreamChanged";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OutputStreamRemoved {
|
||||
pub index: u32,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for OutputStreamRemoved {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
self.index.append_by_ref(i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for OutputStreamRemoved {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(OutputStreamRemoved { index: i.read()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for OutputStreamRemoved {
|
||||
const NAME: &'static str = "OutputStreamRemoved";
|
||||
const INTERFACE: &'static str = "org.Xetibo.ReSetAudio";
|
||||
}
|
||||
|
||||
impl GetVal<(u32,)> for OutputStreamRemoved {
|
||||
fn get_value(&self) -> (u32,) {
|
||||
(self.index,)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_audio_listener(
|
||||
listeners: Arc<Listeners>,
|
||||
sink_box: Option<Arc<SinkBox>>,
|
||||
source_box: Option<Arc<SourceBox>>,
|
||||
) {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
if listeners.pulse_listener.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut conn = start_dbus_audio_listener(conn);
|
||||
|
||||
if let Some(sink_box) = sink_box {
|
||||
conn = start_output_box_listener(conn, sink_box);
|
||||
}
|
||||
if let Some(source_box) = source_box {
|
||||
conn = start_input_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) {
|
||||
stop_dbus_audio_listener(conn);
|
||||
break;
|
||||
}
|
||||
// thread::sleep(Duration::from_millis(1000));
|
||||
// TODO is this really how we should do this?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn start_dbus_audio_listener(conn: Connection) -> Connection {
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call("org.Xetibo.ReSetAudio", "StartAudioListener", ());
|
||||
conn
|
||||
}
|
||||
|
||||
fn stop_dbus_audio_listener(conn: Connection) {
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call("org.Xetibo.ReSetAudio", "StopAudioListener", ());
|
||||
}
|
448
src/components/bluetooth/bluetooth_box.rs
Normal file
448
src/components/bluetooth/bluetooth_box.rs
Normal file
|
@ -0,0 +1,448 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ComboRowExt, ListModelExtManual, PreferencesGroupExt};
|
||||
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::{Error, Path};
|
||||
use glib::{clone, Cast};
|
||||
use gtk::glib::Variant;
|
||||
use gtk::prelude::{ActionableExt, ButtonExt, ListBoxRowExt, WidgetExt};
|
||||
use gtk::{gio, StringObject, Widget};
|
||||
use re_set_lib::bluetooth::bluetooth_structures::{BluetoothAdapter, BluetoothDevice};
|
||||
use re_set_lib::signals::{BluetoothDeviceAdded, BluetoothDeviceChanged, BluetoothDeviceRemoved};
|
||||
|
||||
use crate::components::base::utils::Listeners;
|
||||
use crate::components::bluetooth::bluetooth_box_impl;
|
||||
use crate::components::bluetooth::bluetooth_entry::BluetoothEntry;
|
||||
|
||||
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 BluetoothBox {
|
||||
pub fn new(listeners: Arc<Listeners>) -> Arc<Self> {
|
||||
let obj: Arc<Self> = Arc::new(Object::builder().build());
|
||||
setup_callbacks(listeners, obj)
|
||||
}
|
||||
}
|
||||
|
||||
// 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_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_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);
|
||||
});
|
||||
|
||||
imp.reset_bluetooth_discoverable_switch
|
||||
.connect_state_set(clone!(@weak imp => @default-return glib::Propagation::Proceed,move |_, state| {
|
||||
set_bluetooth_adapter_visibility(imp.reset_current_bluetooth_adapter.borrow().path.clone(), state);
|
||||
glib::Propagation::Proceed
|
||||
}));
|
||||
|
||||
imp.reset_bluetooth_pairable_switch
|
||||
.connect_state_set(clone!(@weak imp => @default-return glib::Propagation::Proceed,move |_, state| {
|
||||
set_bluetooth_adapter_pairability(imp.reset_current_bluetooth_adapter.borrow().path.clone(), state);
|
||||
glib::Propagation::Proceed
|
||||
}));
|
||||
|
||||
imp.reset_bluetooth_switch
|
||||
.connect_state_set(move |_, state| {
|
||||
if !state {
|
||||
let imp = bluetooth_box_ref.imp();
|
||||
for x in imp
|
||||
.reset_bluetooth_connected_devices
|
||||
.observe_children()
|
||||
.iter::<Object>()
|
||||
.flatten()
|
||||
{
|
||||
// todo test this
|
||||
if let Some(item) = x.downcast_ref::<Widget>() {
|
||||
imp.reset_bluetooth_available_devices.remove(item);
|
||||
}
|
||||
}
|
||||
listeners_ref
|
||||
.bluetooth_listener
|
||||
.store(false, Ordering::SeqCst);
|
||||
set_adapter_enabled(
|
||||
imp.reset_current_bluetooth_adapter.borrow().path.clone(),
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
let imp = bluetooth_box_ref.imp();
|
||||
set_adapter_enabled(
|
||||
imp.reset_current_bluetooth_adapter.borrow().path.clone(),
|
||||
true,
|
||||
);
|
||||
start_bluetooth_listener(listeners_ref.clone(), bluetooth_box_ref.clone());
|
||||
}
|
||||
glib::Propagation::Proceed
|
||||
});
|
||||
bluetooth_box
|
||||
}
|
||||
|
||||
pub fn populate_conntected_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) {
|
||||
// TODO handle saved devices -> they also exist
|
||||
gio::spawn_blocking(move || {
|
||||
let ref_box = bluetooth_box.clone();
|
||||
let devices = get_connected_devices();
|
||||
let adapters = get_bluetooth_adapters();
|
||||
{
|
||||
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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
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();
|
||||
imp.reset_bluetooth_switch
|
||||
.set_state(current_adapter.powered);
|
||||
imp.reset_bluetooth_switch
|
||||
.set_active(current_adapter.powered);
|
||||
imp.reset_bluetooth_discoverable_switch
|
||||
.set_state(current_adapter.discoverable);
|
||||
imp.reset_bluetooth_discoverable_switch
|
||||
.set_active(current_adapter.discoverable);
|
||||
imp.reset_bluetooth_pairable_switch
|
||||
.set_state(current_adapter.pairable);
|
||||
imp.reset_bluetooth_pairable_switch
|
||||
.set_active(current_adapter.pairable);
|
||||
}
|
||||
|
||||
imp.reset_bluetooth_adapter.connect_selected_notify(
|
||||
clone!(@weak imp => move |dropdown| {
|
||||
let selected = dropdown.selected_item();
|
||||
if selected.is_none() {
|
||||
return;
|
||||
}
|
||||
let selected = selected.unwrap();
|
||||
let selected = selected.downcast_ref::<StringObject>().unwrap();
|
||||
let selected = selected.string().to_string();
|
||||
|
||||
let device = imp.reset_bluetooth_adapters.read().unwrap();
|
||||
let device = device.get(&selected);
|
||||
if device.is_none() {
|
||||
return;
|
||||
}
|
||||
set_bluetooth_adapter(device.unwrap().0.path.clone());
|
||||
}),
|
||||
);
|
||||
|
||||
for device in devices {
|
||||
let path = device.path.clone();
|
||||
let connected = device.connected;
|
||||
let bluetooth_entry = BluetoothEntry::new(device);
|
||||
imp.available_devices
|
||||
.borrow_mut()
|
||||
.insert(path, bluetooth_entry.clone());
|
||||
if connected {
|
||||
imp.reset_bluetooth_connected_devices.add(&*bluetooth_entry);
|
||||
} else {
|
||||
imp.reset_bluetooth_available_devices.add(&*bluetooth_entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetBluetooth", "StartBluetoothListener", ());
|
||||
let device_added = BluetoothDeviceAdded::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let device_removed = BluetoothDeviceRemoved::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let device_changed = BluetoothDeviceChanged::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
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 res = conn.add_match(device_added, move |ir: BluetoothDeviceAdded, _, _| {
|
||||
let bluetooth_box = device_added_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = bluetooth_box.imp();
|
||||
let path = ir.bluetooth_device.path.clone();
|
||||
let connected = ir.bluetooth_device.connected;
|
||||
let bluetooth_entry = BluetoothEntry::new(ir.bluetooth_device);
|
||||
imp.available_devices
|
||||
.borrow_mut()
|
||||
.insert(path, bluetooth_entry.clone());
|
||||
if connected {
|
||||
imp.reset_bluetooth_connected_devices.add(&*bluetooth_entry);
|
||||
} else {
|
||||
imp.reset_bluetooth_available_devices.add(&*bluetooth_entry);
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on bluetooth device add event");
|
||||
return;
|
||||
}
|
||||
|
||||
let res = conn.add_match(device_removed, move |ir: BluetoothDeviceRemoved, _, _| {
|
||||
let bluetooth_box = device_removed_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = bluetooth_box.imp();
|
||||
let mut map = imp.available_devices.borrow_mut();
|
||||
if let Some(list_entry) = map.remove(&ir.bluetooth_device) {
|
||||
if list_entry.imp().bluetooth_device.borrow().connected {
|
||||
imp.reset_bluetooth_connected_devices.remove(&*list_entry);
|
||||
} else {
|
||||
imp.reset_bluetooth_available_devices.remove(&*list_entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on bluetooth device remove event");
|
||||
return;
|
||||
}
|
||||
|
||||
let res = conn.add_match(device_changed, move |ir: BluetoothDeviceChanged, _, _| {
|
||||
let bluetooth_box = device_changed_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = bluetooth_box.imp();
|
||||
let mut map = imp.available_devices.borrow_mut();
|
||||
if let Some(list_entry) = map.get_mut(&ir.bluetooth_device.path) {
|
||||
let mut existing_bluetooth_device =
|
||||
list_entry.imp().bluetooth_device.borrow_mut();
|
||||
if existing_bluetooth_device.connected != ir.bluetooth_device.connected {
|
||||
if ir.bluetooth_device.connected {
|
||||
imp.reset_bluetooth_connected_devices.add(&**list_entry);
|
||||
imp.reset_bluetooth_available_devices.remove(&**list_entry);
|
||||
} else {
|
||||
imp.reset_bluetooth_available_devices.add(&**list_entry);
|
||||
imp.reset_bluetooth_connected_devices.remove(&**list_entry);
|
||||
}
|
||||
}
|
||||
if existing_bluetooth_device.bonded != ir.bluetooth_device.bonded {
|
||||
if ir.bluetooth_device.bonded {
|
||||
list_entry
|
||||
.imp()
|
||||
.remove_device_button
|
||||
.borrow()
|
||||
.set_sensitive(true);
|
||||
} else {
|
||||
list_entry
|
||||
.imp()
|
||||
.remove_device_button
|
||||
.borrow()
|
||||
.set_sensitive(false);
|
||||
}
|
||||
}
|
||||
*existing_bluetooth_device = ir.bluetooth_device;
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on bluetooth device remove event");
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.bluetooth_listener.store(true, Ordering::SeqCst);
|
||||
let mut time = SystemTime::now();
|
||||
let mut listener_active = true;
|
||||
|
||||
loop {
|
||||
let _ = conn.process(Duration::from_millis(1000));
|
||||
if !listeners.bluetooth_listener.load(Ordering::SeqCst) {
|
||||
let _: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetBluetooth", "StopBluetoothListener", ());
|
||||
break;
|
||||
}
|
||||
if listener_active && time.elapsed().unwrap() > Duration::from_millis(10000) {
|
||||
listener_active = false;
|
||||
let instance_ref = loop_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
instance_ref
|
||||
.imp()
|
||||
.reset_bluetooth_refresh_button
|
||||
.set_sensitive(true);
|
||||
});
|
||||
});
|
||||
let _: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetBluetooth", "StopBluetoothScan", ());
|
||||
}
|
||||
if !listener_active && listeners.bluetooth_scan_requested.load(Ordering::SeqCst) {
|
||||
listeners
|
||||
.bluetooth_scan_requested
|
||||
.store(false, Ordering::SeqCst);
|
||||
listener_active = true;
|
||||
let _: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetBluetooth", "StartBluetoothListener", ());
|
||||
time = SystemTime::now();
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn get_connected_devices() -> Vec<BluetoothDevice> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<BluetoothDevice>,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetBluetooth",
|
||||
"GetConnectedBluetoothDevices",
|
||||
(),
|
||||
);
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_bluetooth_adapters() -> Vec<BluetoothAdapter> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<BluetoothAdapter>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetBluetooth", "GetBluetoothAdapters", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn set_bluetooth_adapter(path: Path<'static>) {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(Vec<BluetoothAdapter>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetBluetooth", "SetBluetoothAdapter", (path,));
|
||||
}
|
||||
|
||||
fn set_bluetooth_adapter_visibility(path: Path<'static>, visible: bool) {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetBluetooth",
|
||||
"SetBluetoothAdapterDiscoverability",
|
||||
(path, visible),
|
||||
);
|
||||
}
|
||||
|
||||
fn set_bluetooth_adapter_pairability(path: Path<'static>, visible: bool) {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetBluetooth",
|
||||
"SetBluetoothAdapterPairability",
|
||||
(path, visible),
|
||||
);
|
||||
}
|
||||
|
||||
fn set_adapter_enabled(path: Path<'static>, enabled: bool) {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(Vec<BluetoothAdapter>,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetBluetooth",
|
||||
"SetBluetoothAdapterEnabled",
|
||||
(path, enabled),
|
||||
);
|
||||
}
|
76
src/components/bluetooth/bluetooth_box_impl.rs
Normal file
76
src/components/bluetooth/bluetooth_box_impl.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use adw::{ActionRow, ComboRow, PreferencesGroup};
|
||||
use dbus::Path;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, Button, CompositeTemplate, Switch};
|
||||
use gtk::{prelude::*, StringList};
|
||||
use re_set_lib::bluetooth::bluetooth_structures::BluetoothAdapter;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
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_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<ListEntry>,
|
||||
#[template_child]
|
||||
pub reset_bluetooth_discoverable_switch: TemplateChild<Switch>,
|
||||
#[template_child]
|
||||
pub reset_bluetooth_pairable_switch: TemplateChild<Switch>,
|
||||
pub available_devices: BluetoothMap,
|
||||
pub connected_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>>,
|
||||
}
|
||||
|
||||
#[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 {}
|
174
src/components/bluetooth/bluetooth_entry.rs
Normal file
174
src/components/bluetooth/bluetooth_entry.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::components::bluetooth::bluetooth_entry_impl;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ActionRowExt, PreferencesRowExt};
|
||||
use adw::{glib, 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;
|
||||
|
||||
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) -> 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());
|
||||
});
|
||||
let gesture = GestureClick::new();
|
||||
// paired is not what we think
|
||||
// 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(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetBluetooth",
|
||||
"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(
|
||||
// "org.Xetibo.ReSetDaemon",
|
||||
// "/org/Xetibo/ReSetDaemon",
|
||||
// Duration::from_millis(1000),
|
||||
// );
|
||||
// let _: Result<(bool,), Error> = proxy.method_call(
|
||||
// "org.Xetibo.ReSetBluetooth",
|
||||
// "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(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetBluetooth",
|
||||
"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>) {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(bool,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetBluetooth", "RemoveDevicePairing", (path,));
|
||||
});
|
||||
}
|
51
src/components/bluetooth/bluetooth_entry_impl.rs
Normal file
51
src/components/bluetooth/bluetooth_entry_impl.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
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::{glib, Button, CompositeTemplate, Label};
|
||||
use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice;
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Default, CompositeTemplate)]
|
||||
#[template(resource = "/org/Xetibo/ReSet/resetBluetoothEntry.ui")]
|
||||
pub struct BluetoothEntry {
|
||||
pub remove_device_button: RefCell<Button>,
|
||||
pub connecting_label: RefCell<Label>,
|
||||
pub device_name: RefCell<String>,
|
||||
pub bluetooth_device: RefCell<BluetoothDevice>
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for BluetoothEntry {
|
||||
const ABSTRACT: bool = false;
|
||||
const NAME: &'static str = "resetBluetoothEntry";
|
||||
type Type = bluetooth_entry::BluetoothEntry;
|
||||
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 BluetoothEntry {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
}
|
||||
}
|
||||
|
||||
impl ActionRowImpl for BluetoothEntry {}
|
||||
|
||||
impl PreferencesRowImpl for BluetoothEntry {}
|
||||
|
||||
impl ListBoxRowImpl for BluetoothEntry {}
|
||||
|
||||
impl WidgetImpl for BluetoothEntry {}
|
||||
|
||||
impl WindowImpl for BluetoothEntry {}
|
||||
|
||||
impl ApplicationWindowImpl for BluetoothEntry {}
|
4
src/components/bluetooth/mod.rs
Normal file
4
src/components/bluetooth/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod bluetooth_box;
|
||||
pub mod bluetooth_box_impl;
|
||||
pub mod bluetooth_entry;
|
||||
pub mod bluetooth_entry_impl;
|
6
src/components/input/mod.rs
Normal file
6
src/components/input/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
pub mod output_stream_entry;
|
||||
pub mod output_stream_entry_impl;
|
||||
pub mod source_box;
|
||||
pub mod source_box_impl;
|
||||
pub mod source_entry;
|
||||
pub mod source_entry_impl;
|
196
src/components/input/output_stream_entry.rs
Normal file
196
src/components/input/output_stream_entry.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::components::utils::{create_dropdown_label_factory, set_combo_row_ellipsis};
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ButtonExt, ComboRowExt, PreferencesRowExt, RangeExt};
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::Error;
|
||||
use glib::subclass::types::ObjectSubclassIsExt;
|
||||
use glib::{clone, Cast, Propagation};
|
||||
use gtk::{gio, StringObject};
|
||||
use re_set_lib::audio::audio_structures::OutputStream;
|
||||
|
||||
use super::output_stream_entry_impl;
|
||||
use super::source_box::SourceBox;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct OutputStreamEntry(ObjectSubclass<output_stream_entry_impl::OutputStreamEntry>)
|
||||
@extends adw::PreferencesGroup, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||
}
|
||||
|
||||
unsafe impl Send for OutputStreamEntry {}
|
||||
unsafe impl Sync for OutputStreamEntry {}
|
||||
|
||||
impl OutputStreamEntry {
|
||||
pub fn new(source_box: Arc<SourceBox>, stream: OutputStream) -> Self {
|
||||
let obj: Self = Object::builder().build();
|
||||
// TODO use event callback for progress bar -> this is the "im speaking" indicator
|
||||
{
|
||||
let box_imp = source_box.imp();
|
||||
let imp = obj.imp();
|
||||
let name = stream.application_name.clone() + ": " + stream.name.as_str();
|
||||
imp.reset_source_selection.set_title(name.as_str());
|
||||
imp.reset_source_selection
|
||||
.set_factory(Some(&create_dropdown_label_factory()));
|
||||
set_combo_row_ellipsis(imp.reset_source_selection.get());
|
||||
let volume = stream.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(*volume as f64);
|
||||
imp.stream.replace(stream);
|
||||
imp.reset_volume_slider.connect_change_value(
|
||||
clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| {
|
||||
let fraction = (value / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
let mut stream = imp.stream.try_borrow();
|
||||
while stream.is_err() {
|
||||
stream = imp.stream.try_borrow();
|
||||
}
|
||||
let stream = stream.unwrap();
|
||||
let index = stream.index;
|
||||
let channels = stream.channels;
|
||||
{
|
||||
let mut time = imp.volume_time_stamp.borrow_mut();
|
||||
if time.is_some()
|
||||
&& time.unwrap().elapsed().unwrap() < Duration::from_millis(50)
|
||||
{
|
||||
return Propagation::Proceed;
|
||||
}
|
||||
*time = Some(SystemTime::now());
|
||||
}
|
||||
set_outputstream_volume(value, index, channels);
|
||||
Propagation::Proceed
|
||||
}),
|
||||
);
|
||||
{
|
||||
let list = box_imp.reset_model_list.read().unwrap();
|
||||
imp.reset_source_selection.set_model(Some(&*list));
|
||||
let map = box_imp.reset_source_map.write().unwrap();
|
||||
let mut name = box_imp.reset_default_source.try_borrow();
|
||||
while name.is_err() {
|
||||
name = box_imp.reset_default_source.try_borrow();
|
||||
}
|
||||
let name = name.unwrap();
|
||||
let name = &name.alias;
|
||||
let index = map.get(name);
|
||||
if let Some(index) = index {
|
||||
imp.reset_source_selection.set_selected(index.1);
|
||||
}
|
||||
}
|
||||
imp.reset_source_selection.connect_selected_notify(
|
||||
clone!(@weak imp, @weak box_imp => move |dropdown| {
|
||||
let selected = dropdown.selected_item();
|
||||
if selected.is_none() {
|
||||
return;
|
||||
}
|
||||
let selected = selected.unwrap();
|
||||
let selected = selected.downcast_ref::<StringObject>().unwrap();
|
||||
let selected = selected.string().to_string();
|
||||
let source = box_imp.reset_source_map.write().unwrap();
|
||||
let source = source.get(&selected);
|
||||
if source.is_none() {
|
||||
return;
|
||||
}
|
||||
let mut stream = imp.stream.try_borrow();
|
||||
while stream.is_err() {
|
||||
stream = imp.stream.try_borrow();
|
||||
}
|
||||
let stream = stream.unwrap();
|
||||
let source = source.unwrap().0;
|
||||
set_source_of_output_stream(stream.index, source);
|
||||
}),
|
||||
);
|
||||
imp.reset_source_mute
|
||||
.connect_clicked(clone!(@weak imp => move |_| {
|
||||
let stream = imp.stream.clone();
|
||||
let mut stream = stream.try_borrow_mut();
|
||||
while stream.is_err() {
|
||||
stream = imp.stream.try_borrow_mut();
|
||||
}
|
||||
let mut stream = stream.unwrap();
|
||||
stream.muted = !stream.muted;
|
||||
let muted = stream.muted;
|
||||
let index = stream.index;
|
||||
if muted {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("microphone-disabled-symbolic");
|
||||
} else {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("audio-input-microphone-symbolic");
|
||||
}
|
||||
toggle_output_stream_mute(index, muted);
|
||||
}));
|
||||
}
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
||||
fn set_outputstream_volume(value: f64, index: u32, channels: u16) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetOutputStreamVolume",
|
||||
(index, channels, value as u32),
|
||||
);
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn toggle_output_stream_mute(index: u32, muted: bool) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetOutputStreamMute",
|
||||
(index, muted),
|
||||
);
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn set_source_of_output_stream(stream: u32, source: u32) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetSourceOfOutputStream",
|
||||
(stream, source),
|
||||
);
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
// TODO propagate error from dbus
|
50
src/components/input/output_stream_entry_impl.rs
Normal file
50
src/components/input/output_stream_entry_impl.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use adw::subclass::prelude::PreferencesGroupImpl;
|
||||
use adw::{ComboRow, PreferencesGroup};
|
||||
use re_set_lib::audio::audio_structures::OutputStream;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::components::input::output_stream_entry;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, Button, CompositeTemplate, Label, ProgressBar, Scale};
|
||||
|
||||
#[derive(Default, CompositeTemplate)]
|
||||
#[template(resource = "/org/Xetibo/ReSet/resetOutputStreamEntry.ui")]
|
||||
pub struct OutputStreamEntry {
|
||||
#[template_child]
|
||||
pub reset_source_selection: TemplateChild<ComboRow>,
|
||||
#[template_child]
|
||||
pub reset_source_mute: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_volume_slider: TemplateChild<Scale>,
|
||||
#[template_child]
|
||||
pub reset_volume_percentage: TemplateChild<Label>,
|
||||
#[template_child]
|
||||
pub reset_volume_meter: TemplateChild<ProgressBar>,
|
||||
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 {}
|
657
src/components/input/source_box.rs
Normal file
657
src/components/input/source_box.rs
Normal file
|
@ -0,0 +1,657 @@
|
|||
use adw::prelude::PreferencesRowExt;
|
||||
use re_set_lib::audio::audio_structures::{Card, OutputStream, Source};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{
|
||||
BoxExt, ButtonExt, CheckButtonExt, ComboRowExt, ListBoxRowExt, PreferencesGroupExt, RangeExt,
|
||||
};
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::{Error, Path};
|
||||
use glib::subclass::prelude::ObjectSubclassIsExt;
|
||||
use glib::{clone, Cast, Propagation, Variant};
|
||||
use gtk::prelude::ActionableExt;
|
||||
use gtk::{gio, StringObject};
|
||||
|
||||
use crate::components::base::card_entry::CardEntry;
|
||||
use crate::components::base::list_entry::ListEntry;
|
||||
use crate::components::base::utils::{
|
||||
OutputStreamAdded, OutputStreamChanged, OutputStreamRemoved, SourceAdded, SourceChanged,
|
||||
SourceRemoved,
|
||||
};
|
||||
use crate::components::input::source_box_impl;
|
||||
use crate::components::input::source_entry::set_source_volume;
|
||||
use crate::components::utils::{create_dropdown_label_factory, set_combo_row_ellipsis};
|
||||
|
||||
use super::output_stream_entry::OutputStreamEntry;
|
||||
use super::source_entry::{set_default_source, toggle_source_mute, SourceEntry};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct SourceBox(ObjectSubclass<source_box_impl::SourceBox>)
|
||||
@extends gtk::Box, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||
}
|
||||
|
||||
unsafe impl Send for SourceBox {}
|
||||
unsafe impl Sync for SourceBox {}
|
||||
|
||||
impl SourceBox {
|
||||
pub fn new() -> Self {
|
||||
let obj: Self = Object::builder().build();
|
||||
{
|
||||
let imp = obj.imp();
|
||||
let mut model_index = imp.reset_model_index.write().unwrap();
|
||||
*model_index = 0;
|
||||
}
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn setup_callbacks(&self) {
|
||||
let self_imp = self.imp();
|
||||
self_imp.reset_source_row.set_activatable(true);
|
||||
self_imp
|
||||
.reset_source_row
|
||||
.set_action_name(Some("navigation.push"));
|
||||
self_imp
|
||||
.reset_source_row
|
||||
.set_action_target_value(Some(&Variant::from("sources")));
|
||||
self_imp.reset_cards_row.set_activatable(true);
|
||||
self_imp
|
||||
.reset_cards_row
|
||||
.set_action_name(Some("navigation.push"));
|
||||
self_imp
|
||||
.reset_cards_row
|
||||
.set_action_target_value(Some(&Variant::from("profileConfiguration")));
|
||||
|
||||
self_imp.reset_output_stream_button.set_activatable(true);
|
||||
self_imp
|
||||
.reset_output_stream_button
|
||||
.set_action_name(Some("navigation.pop"));
|
||||
|
||||
self_imp.reset_input_cards_back_button.set_activatable(true);
|
||||
self_imp
|
||||
.reset_input_cards_back_button
|
||||
.set_action_name(Some("navigation.pop"));
|
||||
|
||||
self_imp
|
||||
.reset_source_dropdown
|
||||
.set_factory(Some(&create_dropdown_label_factory()));
|
||||
set_combo_row_ellipsis(self_imp.reset_source_dropdown.get());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SourceBox {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_sources(input_box: Arc<SourceBox>) {
|
||||
gio::spawn_blocking(move || {
|
||||
let sources = get_sources();
|
||||
{
|
||||
let input_box_imp = input_box.imp();
|
||||
let list = input_box_imp.reset_model_list.write().unwrap();
|
||||
let mut map = input_box_imp.reset_source_map.write().unwrap();
|
||||
let mut model_index = input_box_imp.reset_model_index.write().unwrap();
|
||||
input_box_imp
|
||||
.reset_default_source
|
||||
.replace(get_default_source());
|
||||
for (i, source) in (0_u32..).zip(sources.iter()) {
|
||||
list.append(&source.alias);
|
||||
map.insert(source.alias.clone(), (source.index, i, source.name.clone()));
|
||||
*model_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
populate_outputstreams(input_box.clone());
|
||||
populate_cards(input_box.clone());
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let input_box_ref_slider = input_box.clone();
|
||||
let input_box_ref_toggle = input_box.clone();
|
||||
let input_box_ref_mute = input_box.clone();
|
||||
let input_box_ref = input_box.clone();
|
||||
{
|
||||
let input_box_imp = input_box_ref.imp();
|
||||
let default_sink = input_box_imp.reset_default_source.clone();
|
||||
let source = default_sink.borrow();
|
||||
|
||||
let volume = source.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
input_box_imp.reset_volume_percentage.set_text(&percentage);
|
||||
input_box_imp.reset_volume_slider.set_value(*volume as f64);
|
||||
let mut list = input_box_imp.reset_source_list.write().unwrap();
|
||||
for source in sources {
|
||||
let index = source.index;
|
||||
let alias = source.alias.clone();
|
||||
let mut is_default = false;
|
||||
if input_box_imp.reset_default_source.borrow().name == source.name {
|
||||
is_default = true;
|
||||
}
|
||||
let source_entry = Arc::new(SourceEntry::new(
|
||||
is_default,
|
||||
input_box_imp.reset_default_check_button.clone(),
|
||||
source,
|
||||
input_box.clone(),
|
||||
));
|
||||
let source_clone = source_entry.clone();
|
||||
let entry = Arc::new(ListEntry::new(&*source_entry));
|
||||
entry.set_activatable(false);
|
||||
list.insert(index, (entry.clone(), source_clone, alias));
|
||||
input_box_imp.reset_sources.append(&*entry);
|
||||
}
|
||||
let list = input_box_imp.reset_model_list.read().unwrap();
|
||||
input_box_imp.reset_source_dropdown.set_model(Some(&*list));
|
||||
let map = input_box_imp.reset_source_map.read().unwrap();
|
||||
let name = input_box_imp.reset_default_source.borrow();
|
||||
if let Some(index) = map.get(&name.alias) {
|
||||
input_box_imp.reset_source_dropdown.set_selected(index.1);
|
||||
}
|
||||
input_box_imp.reset_source_dropdown.connect_selected_notify(
|
||||
clone!(@weak input_box_imp => move |dropdown| {
|
||||
let input_box = input_box_ref_toggle.clone();
|
||||
let selected = dropdown.selected_item();
|
||||
if selected.is_none() {
|
||||
return;
|
||||
}
|
||||
let selected = selected.unwrap();
|
||||
let selected = selected.downcast_ref::<StringObject>().unwrap();
|
||||
let selected = selected.string().to_string();
|
||||
|
||||
let source = input_box_imp.reset_source_map.read().unwrap();
|
||||
let source = source.get(&selected);
|
||||
if source.is_none() {
|
||||
return;
|
||||
}
|
||||
let source = Arc::new(source.unwrap().2.clone());
|
||||
gio::spawn_blocking(move || {
|
||||
let result = set_default_source(source);
|
||||
if result.is_none(){
|
||||
return;
|
||||
}
|
||||
refresh_default_source(result.unwrap(), input_box.clone(), false);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
input_box_ref
|
||||
.imp()
|
||||
.reset_volume_slider
|
||||
.connect_change_value(move |_, _, value| {
|
||||
let imp = input_box_ref_slider.imp();
|
||||
let fraction = (value / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
let source = imp.reset_default_source.borrow();
|
||||
let index = source.index;
|
||||
let channels = source.channels;
|
||||
{
|
||||
let mut time = imp.volume_time_stamp.borrow_mut();
|
||||
if time.is_some()
|
||||
&& time.unwrap().elapsed().unwrap() < Duration::from_millis(50)
|
||||
{
|
||||
return Propagation::Proceed;
|
||||
}
|
||||
*time = Some(SystemTime::now());
|
||||
}
|
||||
set_source_volume(value, index, channels);
|
||||
Propagation::Proceed
|
||||
});
|
||||
|
||||
input_box_ref
|
||||
.imp()
|
||||
.reset_source_mute
|
||||
.connect_clicked(move |_| {
|
||||
let imp = input_box_ref_mute.imp();
|
||||
let mut source = imp.reset_default_source.borrow_mut();
|
||||
source.muted = !source.muted;
|
||||
if source.muted {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("microphone-disabled-symbolic");
|
||||
} else {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("audio-input-microphone-symbolic");
|
||||
}
|
||||
toggle_source_mute(source.index, source.muted);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn refresh_default_source(new_source: Source, input_box: Arc<SourceBox>, entry: bool) {
|
||||
let volume = *new_source.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = input_box.imp();
|
||||
if !entry {
|
||||
let list = imp.reset_source_list.read().unwrap();
|
||||
let entry = list.get(&new_source.index);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
let entry_imp = entry.unwrap().1.imp();
|
||||
entry_imp.reset_selected_source.set_active(true);
|
||||
} else {
|
||||
let map = imp.reset_source_map.read().unwrap();
|
||||
let entry = map.get(&new_source.alias);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
imp.reset_source_dropdown.set_selected(entry.unwrap().1);
|
||||
}
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(volume as f64);
|
||||
if new_source.muted {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("microphone-disabled-symbolic");
|
||||
} else {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("audio-input-microphone-symbolic");
|
||||
}
|
||||
imp.reset_default_source.replace(new_source);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn populate_outputstreams(input_box: Arc<SourceBox>) {
|
||||
let input_box_ref = input_box.clone();
|
||||
|
||||
gio::spawn_blocking(move || {
|
||||
let streams = get_output_streams();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let input_box_imp = input_box_ref.imp();
|
||||
let mut list = input_box_imp.reset_output_stream_list.write().unwrap();
|
||||
for stream in streams {
|
||||
let index = stream.index;
|
||||
let input_stream = Arc::new(OutputStreamEntry::new(input_box.clone(), stream));
|
||||
let input_stream_clone = input_stream.clone();
|
||||
let entry = Arc::new(ListEntry::new(&*input_stream));
|
||||
entry.set_activatable(false);
|
||||
list.insert(index, (entry.clone(), input_stream_clone));
|
||||
input_box_imp.reset_output_streams.append(&*entry);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn populate_cards(input_box: Arc<SourceBox>) {
|
||||
gio::spawn_blocking(move || {
|
||||
let input_box_ref = input_box.clone();
|
||||
let cards = get_cards();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = input_box_ref.imp();
|
||||
for card in cards {
|
||||
imp.reset_cards.add(&CardEntry::new(card));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn get_output_streams() -> Vec<OutputStream> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<OutputStream>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "ListOutputStreams", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_sources() -> Vec<Source> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<Source>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "ListSources", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_cards() -> Vec<Card> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<Card>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "ListCards", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_default_source_name() -> String {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(String,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "GetDefaultSourceName", ());
|
||||
if res.is_err() {
|
||||
return String::from("");
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_default_source() -> Source {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Source,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "GetDefaultSource", ());
|
||||
if res.is_err() {
|
||||
return Source::default();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
pub fn start_input_box_listener(conn: Connection, source_box: Arc<SourceBox>) -> Connection {
|
||||
let source_added = SourceAdded::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let source_removed = SourceRemoved::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let source_changed = SourceChanged::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let output_stream_added = OutputStreamAdded::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let output_stream_removed = OutputStreamRemoved::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let output_stream_changed = OutputStreamChanged::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
|
||||
let source_added_box = source_box.clone();
|
||||
let source_removed_box = source_box.clone();
|
||||
let source_changed_box = source_box.clone();
|
||||
let output_stream_added_box = source_box.clone();
|
||||
let output_stream_removed_box = source_box.clone();
|
||||
let output_stream_changed_box = source_box.clone();
|
||||
|
||||
let res = conn.add_match(source_added, move |ir: SourceAdded, _, _| {
|
||||
let source_box = source_added_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let input_box = source_box.clone();
|
||||
let input_box_imp = input_box.imp();
|
||||
let mut list = input_box_imp.reset_source_list.write().unwrap();
|
||||
let source_index = ir.source.index;
|
||||
let alias = ir.source.alias.clone();
|
||||
let name = ir.source.name.clone();
|
||||
let mut is_default = false;
|
||||
if input_box_imp.reset_default_source.borrow().name == ir.source.name {
|
||||
is_default = true;
|
||||
}
|
||||
let source_entry = Arc::new(SourceEntry::new(
|
||||
is_default,
|
||||
input_box_imp.reset_default_check_button.clone(),
|
||||
ir.source,
|
||||
input_box.clone(),
|
||||
));
|
||||
let source_clone = source_entry.clone();
|
||||
let entry = Arc::new(ListEntry::new(&*source_entry));
|
||||
entry.set_activatable(false);
|
||||
list.insert(source_index, (entry.clone(), source_clone, alias.clone()));
|
||||
input_box_imp.reset_sources.append(&*entry);
|
||||
let mut map = input_box_imp.reset_source_map.write().unwrap();
|
||||
let mut index = input_box_imp.reset_model_index.write().unwrap();
|
||||
input_box_imp
|
||||
.reset_model_list
|
||||
.write()
|
||||
.unwrap()
|
||||
.append(&alias);
|
||||
map.insert(alias, (source_index, *index, name));
|
||||
*index += 1;
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on source add event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(source_removed, move |ir: SourceRemoved, _, _| {
|
||||
let source_box = source_removed_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let input_box = source_box.clone();
|
||||
let input_box_imp = input_box.imp();
|
||||
let mut list = input_box_imp.reset_source_list.write().unwrap();
|
||||
let entry = list.remove(&ir.index);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
input_box_imp
|
||||
.reset_sources
|
||||
.remove(&*entry.clone().unwrap().0);
|
||||
let mut map = input_box_imp.reset_source_map.write().unwrap();
|
||||
let entry_index = map.remove(&entry.unwrap().2);
|
||||
if let Some(entry_index) = entry_index {
|
||||
input_box_imp
|
||||
.reset_model_list
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(entry_index.1);
|
||||
}
|
||||
let mut index = input_box_imp.reset_model_index.write().unwrap();
|
||||
if *index != 0 {
|
||||
*index -= 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on source remove event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(source_changed, move |ir: SourceChanged, _, _| {
|
||||
let source_box = source_changed_box.clone();
|
||||
let default_source = get_default_source_name();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let input_box = source_box.clone();
|
||||
let input_box_imp = input_box.imp();
|
||||
let is_default = ir.source.name == default_source;
|
||||
let volume = ir.source.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
|
||||
let list = input_box_imp.reset_source_list.read().unwrap();
|
||||
let entry = list.get(&ir.source.index);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
let imp = entry.unwrap().1.imp();
|
||||
if is_default {
|
||||
input_box_imp.reset_volume_percentage.set_text(&percentage);
|
||||
input_box_imp.reset_volume_slider.set_value(*volume as f64);
|
||||
input_box_imp
|
||||
.reset_default_source
|
||||
.replace(ir.source.clone());
|
||||
if ir.source.muted {
|
||||
input_box_imp
|
||||
.reset_source_mute
|
||||
.set_icon_name("microphone-disabled-symbolic");
|
||||
} else {
|
||||
input_box_imp
|
||||
.reset_source_mute
|
||||
.set_icon_name("audio-input-microphone-symbolic");
|
||||
}
|
||||
imp.reset_selected_source.set_active(true);
|
||||
} else {
|
||||
imp.reset_selected_source.set_active(false);
|
||||
}
|
||||
imp.reset_source_name
|
||||
.set_title(ir.source.alias.clone().as_str());
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(*volume as f64);
|
||||
if ir.source.muted {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("microphone-disabled-symbolic");
|
||||
} else {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("audio-input-microphone-symbolic");
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on source change event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(output_stream_added, move |ir: OutputStreamAdded, _, _| {
|
||||
let source_box = output_stream_added_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let input_box = source_box.clone();
|
||||
let input_box_imp = input_box.imp();
|
||||
let mut list = input_box_imp.reset_output_stream_list.write().unwrap();
|
||||
let index = ir.stream.index;
|
||||
let output_stream = Arc::new(OutputStreamEntry::new(input_box.clone(), ir.stream));
|
||||
let entry = Arc::new(ListEntry::new(&*output_stream));
|
||||
entry.set_activatable(false);
|
||||
list.insert(index, (entry.clone(), output_stream.clone()));
|
||||
input_box_imp.reset_output_streams.append(&*entry);
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on output stream add event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(
|
||||
output_stream_changed,
|
||||
move |ir: OutputStreamChanged, _, _| {
|
||||
let imp = output_stream_changed_box.imp();
|
||||
let alias: String;
|
||||
{
|
||||
let source_list = imp.reset_source_list.read().unwrap();
|
||||
if let Some(alias_opt) = source_list.get(&ir.stream.source_index) {
|
||||
alias = alias_opt.2.clone();
|
||||
} else {
|
||||
alias = String::from("");
|
||||
}
|
||||
}
|
||||
let source_box = output_stream_changed_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let input_box = source_box.clone();
|
||||
let input_box_imp = input_box.imp();
|
||||
let entry: Arc<OutputStreamEntry>;
|
||||
{
|
||||
let list = input_box_imp.reset_output_stream_list.read().unwrap();
|
||||
let entry_opt = list.get(&ir.stream.index);
|
||||
if entry_opt.is_none() {
|
||||
return;
|
||||
}
|
||||
entry = entry_opt.unwrap().1.clone();
|
||||
}
|
||||
let imp = entry.imp();
|
||||
if ir.stream.muted {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("microphone-disabled-symbolic");
|
||||
} else {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("audio-input-microphone-symbolic");
|
||||
}
|
||||
let name = ir.stream.application_name.clone() + ": " + ir.stream.name.as_str();
|
||||
imp.reset_source_selection.set_title(name.as_str());
|
||||
let volume = ir.stream.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(*volume as f64);
|
||||
let map = input_box_imp.reset_source_map.read().unwrap();
|
||||
if let Some(index) = map.get(&alias) {
|
||||
imp.reset_source_selection.set_selected(index.1);
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
},
|
||||
);
|
||||
if res.is_err() {
|
||||
println!("fail on output stream change event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(
|
||||
output_stream_removed,
|
||||
move |ir: OutputStreamRemoved, _, _| {
|
||||
let source_box = output_stream_removed_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let input_box = source_box.clone();
|
||||
let input_box_imp = input_box.imp();
|
||||
let mut list = input_box_imp.reset_output_stream_list.write().unwrap();
|
||||
let entry = list.remove(&ir.index);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
input_box_imp
|
||||
.reset_output_streams
|
||||
.remove(&*entry.unwrap().0);
|
||||
});
|
||||
});
|
||||
true
|
||||
},
|
||||
);
|
||||
if res.is_err() {
|
||||
println!("fail on output stream remove event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
conn
|
||||
}
|
94
src/components/input/source_box_impl.rs
Normal file
94
src/components/input/source_box_impl.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
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::base::list_entry::ListEntry;
|
||||
use crate::components::input::source_box;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, CheckButton, CompositeTemplate, StringList, TemplateChild};
|
||||
use gtk::{prelude::*, Button, Label, ProgressBar, Scale};
|
||||
|
||||
use super::output_stream_entry::OutputStreamEntry;
|
||||
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>)>>>;
|
||||
type SourceMap = Arc<RwLock<HashMap<String, (u32, 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_volume_meter: TemplateChild<ProgressBar>,
|
||||
#[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>,
|
||||
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>>,
|
||||
// first u32 is the index of the source, the second the index in the model list and the third is
|
||||
// the full name
|
||||
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 {
|
||||
fn constructed(&self) {
|
||||
let obj = self.obj();
|
||||
obj.setup_callbacks();
|
||||
}
|
||||
}
|
||||
|
||||
impl ListBoxRowImpl for SourceBox {}
|
||||
|
||||
impl WidgetImpl for SourceBox {}
|
||||
|
||||
impl WindowImpl for SourceBox {}
|
||||
|
||||
impl ApplicationWindowImpl for SourceBox {}
|
159
src/components/input/source_entry.rs
Normal file
159
src/components/input/source_entry.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ButtonExt, CheckButtonExt, PreferencesRowExt, RangeExt};
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::Error;
|
||||
use glib::subclass::types::ObjectSubclassIsExt;
|
||||
use glib::{clone, Propagation};
|
||||
use gtk::{gio, CheckButton};
|
||||
use re_set_lib::audio::audio_structures::Source;
|
||||
|
||||
use super::source_box::{refresh_default_source, SourceBox};
|
||||
use super::source_entry_impl;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct SourceEntry(ObjectSubclass<source_entry_impl::SourceEntry>)
|
||||
@extends adw::PreferencesGroup, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||
}
|
||||
|
||||
unsafe impl Send for SourceEntry {}
|
||||
unsafe impl Sync for SourceEntry {}
|
||||
|
||||
impl SourceEntry {
|
||||
pub fn new(
|
||||
is_default: bool,
|
||||
check_group: Arc<CheckButton>,
|
||||
source: Source,
|
||||
input_box: Arc<SourceBox>,
|
||||
) -> Self {
|
||||
let obj: Self = Object::builder().build();
|
||||
// TODO use event callback for progress bar -> this is the "im speaking" indicator
|
||||
{
|
||||
let imp = obj.imp();
|
||||
imp.reset_source_name
|
||||
.set_title(source.alias.clone().as_str());
|
||||
let name = Arc::new(source.name.clone());
|
||||
let volume = source.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(*volume as f64);
|
||||
imp.source.replace(source);
|
||||
imp.reset_volume_slider.connect_change_value(
|
||||
clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| {
|
||||
let fraction = (value / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
let source = imp.source.borrow();
|
||||
let index = source.index;
|
||||
let channels = source.channels;
|
||||
{
|
||||
let mut time = imp.volume_time_stamp.borrow_mut();
|
||||
if time.is_some()
|
||||
&& time.unwrap().elapsed().unwrap() < Duration::from_millis(50)
|
||||
{
|
||||
return Propagation::Proceed;
|
||||
}
|
||||
*time = Some(SystemTime::now());
|
||||
}
|
||||
set_source_volume(value, index, channels);
|
||||
Propagation::Proceed
|
||||
}),
|
||||
);
|
||||
imp.reset_selected_source.set_group(Some(&*check_group));
|
||||
if is_default {
|
||||
imp.reset_selected_source.set_active(true);
|
||||
} else {
|
||||
imp.reset_selected_source.set_active(false);
|
||||
}
|
||||
imp.reset_selected_source.connect_toggled(move |button| {
|
||||
let input_box = input_box.clone();
|
||||
if button.is_active() {
|
||||
let name = name.clone();
|
||||
gio::spawn_blocking(move || {
|
||||
let result = set_default_source(name);
|
||||
if result.is_none() {
|
||||
return;
|
||||
}
|
||||
refresh_default_source(result.unwrap(), input_box, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
imp.reset_source_mute
|
||||
.connect_clicked(clone!(@weak imp => move |_| {
|
||||
let mut source = imp.source.borrow_mut();
|
||||
source.muted = !source.muted;
|
||||
if source.muted {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("microphone-disabled-symbolic");
|
||||
} else {
|
||||
imp.reset_source_mute
|
||||
.set_icon_name("audio-input-microphone-symbolic");
|
||||
}
|
||||
toggle_source_mute(source.index, source.muted);
|
||||
}));
|
||||
}
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_source_volume(value: f64, index: u32, channels: u16) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetSourceVolume",
|
||||
(index, channels, value as u32),
|
||||
);
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
pub fn toggle_source_mute(index: u32, muted: bool) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "SetSourceMute", (index, muted));
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_default_source(name: Arc<String>) -> Option<Source> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Source,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetDefaultSource",
|
||||
(name.as_str(),),
|
||||
);
|
||||
if res.is_err() {
|
||||
return None;
|
||||
}
|
||||
Some(res.unwrap().0)
|
||||
}
|
52
src/components/input/source_entry_impl.rs
Normal file
52
src/components/input/source_entry_impl.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
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::{glib, Button, CheckButton, CompositeTemplate, Label, ProgressBar, Scale};
|
||||
|
||||
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>,
|
||||
#[template_child]
|
||||
pub reset_volume_meter: TemplateChild<ProgressBar>,
|
||||
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 {}
|
7
src/components/mod.rs
Normal file
7
src/components/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod base;
|
||||
pub mod bluetooth;
|
||||
mod input;
|
||||
pub mod output;
|
||||
pub mod utils;
|
||||
pub mod wifi;
|
||||
pub mod window;
|
0
src/components/output/audio_box.rs
Normal file
0
src/components/output/audio_box.rs
Normal file
223
src/components/output/input_stream_entry.rs
Normal file
223
src/components/output/input_stream_entry.rs
Normal file
|
@ -0,0 +1,223 @@
|
|||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::components::utils::{create_dropdown_label_factory, set_combo_row_ellipsis};
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ButtonExt, ComboRowExt, PreferencesRowExt, RangeExt};
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::Error;
|
||||
use glib::subclass::types::ObjectSubclassIsExt;
|
||||
use glib::{clone, Cast, Propagation};
|
||||
use gtk::{gio, StringObject};
|
||||
use re_set_lib::audio::audio_structures::InputStream;
|
||||
|
||||
use super::input_stream_entry_impl;
|
||||
use super::sink_box::SinkBox;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct InputStreamEntry(ObjectSubclass<input_stream_entry_impl::InputStreamEntry>)
|
||||
@extends adw::PreferencesGroup, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||
}
|
||||
|
||||
unsafe impl Send for InputStreamEntry {}
|
||||
unsafe impl Sync for InputStreamEntry {}
|
||||
|
||||
impl InputStreamEntry {
|
||||
pub fn new(sink_box: Arc<SinkBox>, stream: InputStream) -> Self {
|
||||
let obj: Self = Object::builder().build();
|
||||
// TODO use event callback for progress bar -> this is the "im speaking" indicator
|
||||
{
|
||||
let index = stream.sink_index;
|
||||
let box_imp = sink_box.imp();
|
||||
let imp = obj.imp();
|
||||
if stream.muted {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-muted-symbolic");
|
||||
} else {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-high-symbolic");
|
||||
}
|
||||
let name = stream.application_name.clone() + ": " + stream.name.as_str();
|
||||
imp.reset_sink_selection.set_title(name.as_str());
|
||||
imp.reset_sink_selection
|
||||
.set_factory(Some(&create_dropdown_label_factory()));
|
||||
set_combo_row_ellipsis(imp.reset_sink_selection.get());
|
||||
let volume = stream.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(*volume as f64);
|
||||
imp.stream.replace(stream);
|
||||
{
|
||||
let sink = box_imp.reset_default_sink.borrow();
|
||||
imp.associated_sink.replace((sink.index, sink.name.clone()));
|
||||
}
|
||||
imp.reset_volume_slider.connect_change_value(
|
||||
clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| {
|
||||
let fraction = (value / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
let mut stream = imp.stream.try_borrow();
|
||||
while stream.is_err() {
|
||||
stream = imp.stream.try_borrow();
|
||||
}
|
||||
let stream = stream.unwrap();
|
||||
let index = stream.index;
|
||||
let channels = stream.channels;
|
||||
{
|
||||
let mut time = imp.volume_time_stamp.borrow_mut();
|
||||
if time.is_some() && time.unwrap().elapsed().unwrap() < Duration::from_millis(50) {
|
||||
return Propagation::Proceed;
|
||||
}
|
||||
*time = Some(SystemTime::now());
|
||||
}
|
||||
set_inputstream_volume(value, index, channels);
|
||||
Propagation::Proceed
|
||||
}),
|
||||
);
|
||||
{
|
||||
let list = box_imp.reset_model_list.read().unwrap();
|
||||
// while list.is_err() {
|
||||
// list = box_imp.resetModelList.try_borrow();
|
||||
// }
|
||||
// let list = list.unwrap();
|
||||
imp.reset_sink_selection.set_model(Some(&*list));
|
||||
let map = box_imp.reset_sink_map.read().unwrap();
|
||||
let sink_list = box_imp.reset_sink_list.read().unwrap();
|
||||
let name = sink_list.get(&index);
|
||||
if let Some(name) = name {
|
||||
let name = &name.2;
|
||||
let index = map.get(name);
|
||||
if let Some(index) = index {
|
||||
imp.reset_sink_selection.set_selected(index.1);
|
||||
}
|
||||
} else {
|
||||
let mut name = box_imp.reset_default_sink.try_borrow();
|
||||
while name.is_err() {
|
||||
name = box_imp.reset_default_sink.try_borrow();
|
||||
}
|
||||
let name = &name.unwrap().alias;
|
||||
let index = map.get(name);
|
||||
if let Some(index) = index {
|
||||
imp.reset_sink_selection.set_selected(index.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
imp.reset_sink_selection.connect_selected_notify(
|
||||
clone!(@weak imp, @weak box_imp => move |dropdown| {
|
||||
let selected = dropdown.selected_item();
|
||||
if selected.is_none() {
|
||||
return;
|
||||
}
|
||||
let selected = selected.unwrap();
|
||||
let selected = selected.downcast_ref::<StringObject>().unwrap();
|
||||
let selected = selected.string().to_string();
|
||||
let sink = box_imp.reset_sink_map.read().unwrap();
|
||||
// if sink.is_err() {
|
||||
// return;
|
||||
// }
|
||||
// let sink = sink.unwrap();
|
||||
let sink = sink.get(&selected);
|
||||
if sink.is_none() {
|
||||
return;
|
||||
}
|
||||
let mut stream = imp.stream.try_borrow();
|
||||
while stream.is_err() {
|
||||
stream = imp.stream.try_borrow();
|
||||
}
|
||||
let stream = stream.unwrap();
|
||||
let sink = sink.unwrap().0;
|
||||
set_sink_of_input_stream(stream.index, sink);
|
||||
}),
|
||||
);
|
||||
imp.reset_sink_mute
|
||||
.connect_clicked(clone!(@weak imp => move |_| {
|
||||
let stream = imp.stream.clone();
|
||||
let mut stream = stream.try_borrow_mut();
|
||||
while stream.is_err() {
|
||||
stream = imp.stream.try_borrow_mut();
|
||||
}
|
||||
let mut stream = stream.unwrap();
|
||||
stream.muted = !stream.muted;
|
||||
let muted = stream.muted;
|
||||
let index = stream.index;
|
||||
if muted {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-muted-symbolic");
|
||||
} else {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-high-symbolic");
|
||||
}
|
||||
toggle_input_stream_mute(index, muted);
|
||||
}));
|
||||
}
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
||||
fn set_inputstream_volume(value: f64, index: u32, channels: u16) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetInputStreamVolume",
|
||||
(index, channels, value as u32),
|
||||
);
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn toggle_input_stream_mute(index: u32, muted: bool) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetInputStreamMute",
|
||||
(index, muted),
|
||||
);
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn set_sink_of_input_stream(stream: u32, sink: u32) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetSinkOfInputStream",
|
||||
(stream, sink),
|
||||
);
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
// TODO propagate error from dbus
|
51
src/components/output/input_stream_entry_impl.rs
Normal file
51
src/components/output/input_stream_entry_impl.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use adw::subclass::prelude::PreferencesGroupImpl;
|
||||
use adw::{ComboRow, PreferencesGroup};
|
||||
use re_set_lib::audio::audio_structures::InputStream;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, Button, CompositeTemplate, Label, ProgressBar, Scale};
|
||||
|
||||
use super::input_stream_entry;
|
||||
|
||||
#[derive(Default, CompositeTemplate)]
|
||||
#[template(resource = "/org/Xetibo/ReSet/resetInputStreamEntry.ui")]
|
||||
pub struct InputStreamEntry {
|
||||
#[template_child]
|
||||
pub reset_sink_selection: TemplateChild<ComboRow>,
|
||||
#[template_child]
|
||||
pub reset_sink_mute: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_volume_slider: TemplateChild<Scale>,
|
||||
#[template_child]
|
||||
pub reset_volume_percentage: TemplateChild<Label>,
|
||||
#[template_child]
|
||||
pub reset_volume_meter: TemplateChild<ProgressBar>,
|
||||
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 {}
|
7
src/components/output/mod.rs
Normal file
7
src/components/output/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
pub mod input_stream_entry;
|
||||
pub mod input_stream_entry_impl;
|
||||
pub mod sink_box;
|
||||
pub mod sink_box_impl;
|
||||
pub mod sink_entry;
|
||||
pub mod sink_entry_impl;
|
||||
pub mod audio_box;
|
647
src/components/output/sink_box.rs
Normal file
647
src/components/output/sink_box.rs
Normal file
|
@ -0,0 +1,647 @@
|
|||
use adw::prelude::PreferencesGroupExt;
|
||||
use adw::prelude::PreferencesRowExt;
|
||||
use re_set_lib::audio::audio_structures::Card;
|
||||
use re_set_lib::audio::audio_structures::InputStream;
|
||||
use re_set_lib::audio::audio_structures::Sink;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{BoxExt, ButtonExt, CheckButtonExt, ComboRowExt, RangeExt};
|
||||
use adw::{glib, prelude::ListBoxRowExt};
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::{Error, Path};
|
||||
use glib::subclass::prelude::ObjectSubclassIsExt;
|
||||
use glib::{clone, Cast, Propagation, Variant};
|
||||
use gtk::prelude::ActionableExt;
|
||||
use gtk::{gio, StringObject};
|
||||
|
||||
use crate::components::base::card_entry::CardEntry;
|
||||
use crate::components::base::list_entry::ListEntry;
|
||||
use crate::components::base::utils::{
|
||||
InputStreamAdded, InputStreamChanged, InputStreamRemoved, SinkAdded, SinkChanged, SinkRemoved,
|
||||
};
|
||||
use crate::components::output::sink_entry::set_sink_volume;
|
||||
use crate::components::utils::{create_dropdown_label_factory, set_combo_row_ellipsis};
|
||||
|
||||
use super::input_stream_entry::InputStreamEntry;
|
||||
use super::sink_box_impl;
|
||||
use super::sink_entry::{set_default_sink, toggle_sink_mute, SinkEntry};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct SinkBox(ObjectSubclass<sink_box_impl::SinkBox>)
|
||||
@extends gtk::Box, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||
}
|
||||
|
||||
unsafe impl Send for SinkBox {}
|
||||
unsafe impl Sync for SinkBox {}
|
||||
|
||||
impl SinkBox {
|
||||
pub fn new() -> Self {
|
||||
let obj: Self = Object::builder().build();
|
||||
{
|
||||
let imp = obj.imp();
|
||||
let mut model_index = imp.reset_model_index.write().unwrap();
|
||||
*model_index = 0;
|
||||
}
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn setup_callbacks(&self) {
|
||||
let self_imp = self.imp();
|
||||
self_imp.reset_sinks_row.set_activatable(true);
|
||||
self_imp
|
||||
.reset_sinks_row
|
||||
.set_action_name(Some("navigation.push"));
|
||||
self_imp
|
||||
.reset_sinks_row
|
||||
.set_action_target_value(Some(&Variant::from("outputDevices")));
|
||||
self_imp.reset_cards_row.set_activatable(true);
|
||||
self_imp
|
||||
.reset_cards_row
|
||||
.set_action_name(Some("navigation.push"));
|
||||
self_imp
|
||||
.reset_cards_row
|
||||
.set_action_target_value(Some(&Variant::from("profileConfiguration")));
|
||||
|
||||
self_imp.reset_input_stream_button.set_activatable(true);
|
||||
self_imp
|
||||
.reset_input_stream_button
|
||||
.set_action_name(Some("navigation.pop"));
|
||||
|
||||
self_imp.reset_input_cards_back_button.set_activatable(true);
|
||||
self_imp
|
||||
.reset_input_cards_back_button
|
||||
.set_action_name(Some("navigation.pop"));
|
||||
|
||||
self_imp
|
||||
.reset_sink_dropdown
|
||||
.set_factory(Some(&create_dropdown_label_factory()));
|
||||
set_combo_row_ellipsis(self_imp.reset_sink_dropdown.get());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SinkBox {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_sinks(output_box: Arc<SinkBox>) {
|
||||
gio::spawn_blocking(move || {
|
||||
let sinks = get_sinks();
|
||||
{
|
||||
let output_box_imp = output_box.imp();
|
||||
let list = output_box_imp.reset_model_list.write().unwrap();
|
||||
let mut map = output_box_imp.reset_sink_map.write().unwrap();
|
||||
let mut model_index = output_box_imp.reset_model_index.write().unwrap();
|
||||
output_box_imp
|
||||
.reset_default_sink
|
||||
.replace(get_default_sink());
|
||||
for (i, sink) in (0_u32..).zip(sinks.iter()) {
|
||||
list.append(&sink.alias);
|
||||
map.insert(sink.alias.clone(), (sink.index, i, sink.name.clone()));
|
||||
*model_index += 1;
|
||||
}
|
||||
}
|
||||
populate_inputstreams(output_box.clone());
|
||||
populate_cards(output_box.clone());
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let output_box_ref_select = output_box.clone();
|
||||
let output_box_ref_slider = output_box.clone();
|
||||
let output_box_ref_mute = output_box.clone();
|
||||
let output_box_ref = output_box.clone();
|
||||
{
|
||||
let output_box_imp = output_box_ref.imp();
|
||||
let default_sink = output_box_imp.reset_default_sink.clone();
|
||||
let sink = default_sink.borrow();
|
||||
|
||||
let volume = sink.volume.first().unwrap_or(&0);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
output_box_imp.reset_volume_percentage.set_text(&percentage);
|
||||
output_box_imp.reset_volume_slider.set_value(*volume as f64);
|
||||
let mut list = output_box_imp.reset_sink_list.write().unwrap();
|
||||
for sink in sinks {
|
||||
let index = sink.index;
|
||||
let alias = sink.alias.clone();
|
||||
let mut is_default = false;
|
||||
if output_box_imp.reset_default_sink.borrow().name == sink.name {
|
||||
is_default = true;
|
||||
}
|
||||
let sink_entry = Arc::new(SinkEntry::new(
|
||||
is_default,
|
||||
output_box_imp.reset_default_check_button.clone(),
|
||||
sink,
|
||||
output_box.clone(),
|
||||
));
|
||||
let sink_clone = sink_entry.clone();
|
||||
let entry = Arc::new(ListEntry::new(&*sink_entry));
|
||||
entry.set_activatable(false);
|
||||
list.insert(index, (entry.clone(), sink_clone, alias));
|
||||
output_box_imp.reset_sinks.append(&*entry);
|
||||
}
|
||||
let list = output_box_imp.reset_model_list.read().unwrap();
|
||||
output_box_imp.reset_sink_dropdown.set_model(Some(&*list));
|
||||
let map = output_box_imp.reset_sink_map.read().unwrap();
|
||||
let name = output_box_imp.reset_default_sink.borrow();
|
||||
if let Some(index) = map.get(&name.alias) {
|
||||
output_box_imp.reset_sink_dropdown.set_selected(index.1);
|
||||
}
|
||||
output_box_imp.reset_sink_dropdown.connect_selected_notify(
|
||||
clone!(@weak output_box_imp => move |dropdown| {
|
||||
let output_box_ref = output_box_ref_select.clone();
|
||||
let selected = dropdown.selected_item();
|
||||
if selected.is_none() {
|
||||
return;
|
||||
}
|
||||
let selected = selected.unwrap();
|
||||
let selected = selected.downcast_ref::<StringObject>().unwrap();
|
||||
let selected = selected.string().to_string();
|
||||
|
||||
let sink = output_box_imp.reset_sink_map.read().unwrap();
|
||||
let sink = sink.get(&selected);
|
||||
if sink.is_none() {
|
||||
return;
|
||||
}
|
||||
let new_sink_name = Arc::new(sink.unwrap().2.clone());
|
||||
gio::spawn_blocking(move || {
|
||||
let result = set_default_sink(new_sink_name);
|
||||
if result.is_none() {
|
||||
return;
|
||||
}
|
||||
let new_sink = result.unwrap();
|
||||
refresh_default_sink(new_sink, output_box_ref, false);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
output_box_ref
|
||||
.imp()
|
||||
.reset_volume_slider
|
||||
.connect_change_value(move |_, _, value| {
|
||||
let imp = output_box_ref_slider.imp();
|
||||
let fraction = (value / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
let sink = imp.reset_default_sink.borrow();
|
||||
let index = sink.index;
|
||||
let channels = sink.channels;
|
||||
{
|
||||
let mut time = imp.volume_time_stamp.borrow_mut();
|
||||
if time.is_some()
|
||||
&& time.unwrap().elapsed().unwrap() < Duration::from_millis(50)
|
||||
{
|
||||
return Propagation::Proceed;
|
||||
}
|
||||
*time = Some(SystemTime::now());
|
||||
}
|
||||
set_sink_volume(value, index, channels);
|
||||
Propagation::Proceed
|
||||
});
|
||||
output_box_ref
|
||||
.imp()
|
||||
.reset_sink_mute
|
||||
.connect_clicked(move |_| {
|
||||
let imp = output_box_ref_mute.imp();
|
||||
let mut stream = imp.reset_default_sink.borrow_mut();
|
||||
stream.muted = !stream.muted;
|
||||
if stream.muted {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-muted-symbolic");
|
||||
} else {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-high-symbolic");
|
||||
}
|
||||
toggle_sink_mute(stream.index, stream.muted);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn refresh_default_sink(new_sink: Sink, output_box: Arc<SinkBox>, entry: bool) {
|
||||
let volume = *new_sink.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = output_box.imp();
|
||||
if !entry {
|
||||
let list = imp.reset_sink_list.read().unwrap();
|
||||
let entry = list.get(&new_sink.index);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
let entry_imp = entry.unwrap().1.imp();
|
||||
entry_imp.reset_selected_sink.set_active(true);
|
||||
} else {
|
||||
let map = imp.reset_sink_map.read().unwrap();
|
||||
let entry = map.get(&new_sink.alias);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
imp.reset_sink_dropdown.set_selected(entry.unwrap().1);
|
||||
}
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(volume as f64);
|
||||
if new_sink.muted {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-muted-symbolic");
|
||||
} else {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-high-symbolic");
|
||||
}
|
||||
imp.reset_default_sink.replace(new_sink);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn populate_inputstreams(output_box: Arc<SinkBox>) {
|
||||
let output_box_ref = output_box.clone();
|
||||
|
||||
gio::spawn_blocking(move || {
|
||||
let streams = get_input_streams();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let output_box_imp = output_box_ref.imp();
|
||||
let mut list = output_box_imp.reset_input_stream_list.write().unwrap();
|
||||
for stream in streams {
|
||||
let index = stream.index;
|
||||
let input_stream = Arc::new(InputStreamEntry::new(output_box.clone(), stream));
|
||||
let entry = Arc::new(ListEntry::new(&*input_stream));
|
||||
entry.set_activatable(false);
|
||||
list.insert(index, (entry.clone(), input_stream.clone()));
|
||||
output_box_imp.reset_input_streams.append(&*entry);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn populate_cards(output_box: Arc<SinkBox>) {
|
||||
gio::spawn_blocking(move || {
|
||||
let output_box_ref = output_box.clone();
|
||||
let cards = get_cards();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = output_box_ref.imp();
|
||||
for card in cards {
|
||||
imp.reset_cards.add(&CardEntry::new(card));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn get_input_streams() -> Vec<InputStream> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<InputStream>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "ListInputStreams", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_sinks() -> Vec<Sink> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<Sink>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "ListSinks", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_cards() -> Vec<Card> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<Card>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "ListCards", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_default_sink_name() -> String {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(String,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "GetDefaultSinkName", ());
|
||||
if res.is_err() {
|
||||
return String::from("");
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
fn get_default_sink() -> Sink {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Sink,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "GetDefaultSink", ());
|
||||
if res.is_err() {
|
||||
return Sink::default();
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
pub fn start_output_box_listener(conn: Connection, sink_box: Arc<SinkBox>) -> Connection {
|
||||
let sink_added = SinkAdded::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let sink_removed = SinkRemoved::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let sink_changed = SinkChanged::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let input_stream_added = InputStreamAdded::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let input_stream_removed = InputStreamRemoved::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let input_stream_changed = InputStreamChanged::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
|
||||
let sink_added_box = sink_box.clone();
|
||||
let sink_removed_box = sink_box.clone();
|
||||
let sink_changed_box = sink_box.clone();
|
||||
let input_stream_added_box = sink_box.clone();
|
||||
let input_stream_removed_box = sink_box.clone();
|
||||
let input_stream_changed_box = sink_box.clone();
|
||||
|
||||
let res = conn.add_match(sink_added, move |ir: SinkAdded, _, _| {
|
||||
let sink_box = sink_added_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let output_box = sink_box.clone();
|
||||
let output_box_imp = output_box.imp();
|
||||
let mut list = output_box_imp.reset_sink_list.write().unwrap();
|
||||
let sink_index = ir.sink.index;
|
||||
let alias = ir.sink.alias.clone();
|
||||
let name = ir.sink.name.clone();
|
||||
let mut is_default = false;
|
||||
if output_box_imp.reset_default_sink.borrow().name == ir.sink.name {
|
||||
is_default = true;
|
||||
}
|
||||
let sink_entry = Arc::new(SinkEntry::new(
|
||||
is_default,
|
||||
output_box_imp.reset_default_check_button.clone(),
|
||||
ir.sink,
|
||||
output_box.clone(),
|
||||
));
|
||||
let sink_clone = sink_entry.clone();
|
||||
let entry = Arc::new(ListEntry::new(&*sink_entry));
|
||||
entry.set_activatable(false);
|
||||
list.insert(sink_index, (entry.clone(), sink_clone, alias.clone()));
|
||||
output_box_imp.reset_sinks.append(&*entry);
|
||||
let mut map = output_box_imp.reset_sink_map.write().unwrap();
|
||||
let mut index = output_box_imp.reset_model_index.write().unwrap();
|
||||
output_box_imp
|
||||
.reset_model_list
|
||||
.write()
|
||||
.unwrap()
|
||||
.append(&alias);
|
||||
map.insert(alias, (sink_index, *index, name));
|
||||
*index += 1;
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on sink add event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(sink_removed, move |ir: SinkRemoved, _, _| {
|
||||
let sink_box = sink_removed_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let output_box = sink_box.clone();
|
||||
let output_box_imp = output_box.imp();
|
||||
let mut list = output_box_imp.reset_sink_list.write().unwrap();
|
||||
let entry = list.remove(&ir.index);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
output_box_imp
|
||||
.reset_sinks
|
||||
.remove(&*entry.clone().unwrap().0);
|
||||
let mut map = output_box_imp.reset_sink_map.write().unwrap();
|
||||
let entry_index = map.remove(&entry.unwrap().2);
|
||||
if let Some(entry_index) = entry_index {
|
||||
output_box_imp
|
||||
.reset_model_list
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(entry_index.1);
|
||||
}
|
||||
let mut index = output_box_imp.reset_model_index.write().unwrap();
|
||||
if *index != 0 {
|
||||
*index -= 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on sink remove event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(sink_changed, move |ir: SinkChanged, _, _| {
|
||||
let sink_box = sink_changed_box.clone();
|
||||
let default_sink = get_default_sink_name();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let output_box = sink_box.clone();
|
||||
let output_box_imp = output_box.imp();
|
||||
let is_default = ir.sink.name == default_sink;
|
||||
let volume = ir.sink.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
|
||||
let list = output_box_imp.reset_sink_list.read().unwrap();
|
||||
let entry = list.get(&ir.sink.index);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
let imp = entry.unwrap().1.imp();
|
||||
if is_default {
|
||||
output_box_imp.reset_volume_percentage.set_text(&percentage);
|
||||
output_box_imp.reset_volume_slider.set_value(*volume as f64);
|
||||
output_box_imp.reset_default_sink.replace(ir.sink.clone());
|
||||
if ir.sink.muted {
|
||||
output_box_imp
|
||||
.reset_sink_mute
|
||||
.set_icon_name("audio-volume-muted-symbolic");
|
||||
} else {
|
||||
output_box_imp
|
||||
.reset_sink_mute
|
||||
.set_icon_name("audio-volume-high-symbolic");
|
||||
}
|
||||
imp.reset_selected_sink.set_active(true);
|
||||
} else {
|
||||
imp.reset_selected_sink.set_active(false);
|
||||
}
|
||||
imp.reset_sink_name
|
||||
.set_title(ir.sink.alias.clone().as_str());
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(*volume as f64);
|
||||
if ir.sink.muted {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-muted-symbolic");
|
||||
} else {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-high-symbolic");
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on sink change event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(input_stream_added, move |ir: InputStreamAdded, _, _| {
|
||||
let sink_box = input_stream_added_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let output_box = sink_box.clone();
|
||||
let output_box_imp = output_box.imp();
|
||||
let mut list = output_box_imp.reset_input_stream_list.write().unwrap();
|
||||
let index = ir.stream.index;
|
||||
let input_stream = Arc::new(InputStreamEntry::new(output_box.clone(), ir.stream));
|
||||
let entry = Arc::new(ListEntry::new(&*input_stream));
|
||||
entry.set_activatable(false);
|
||||
list.insert(index, (entry.clone(), input_stream.clone()));
|
||||
output_box_imp.reset_input_streams.append(&*entry);
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on input stream add event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(input_stream_changed, move |ir: InputStreamChanged, _, _| {
|
||||
let imp = input_stream_changed_box.imp();
|
||||
let alias: String;
|
||||
{
|
||||
let sink_list = imp.reset_sink_list.read().unwrap();
|
||||
if let Some(alias_opt) = sink_list.get(&ir.stream.sink_index) {
|
||||
alias = alias_opt.2.clone();
|
||||
} else {
|
||||
alias = String::from("");
|
||||
}
|
||||
}
|
||||
let sink_box = input_stream_changed_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let output_box = sink_box.clone();
|
||||
let output_box_imp = output_box.imp();
|
||||
let entry: Arc<InputStreamEntry>;
|
||||
{
|
||||
let list = output_box_imp.reset_input_stream_list.read().unwrap();
|
||||
let entry_opt = list.get(&ir.stream.index);
|
||||
if entry_opt.is_none() {
|
||||
return;
|
||||
}
|
||||
entry = entry_opt.unwrap().1.clone();
|
||||
}
|
||||
let imp = entry.imp();
|
||||
if ir.stream.muted {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-muted-symbolic");
|
||||
} else {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-high-symbolic");
|
||||
}
|
||||
let name = ir.stream.application_name.clone() + ": " + ir.stream.name.as_str();
|
||||
imp.reset_sink_selection.set_title(name.as_str());
|
||||
let volume = ir.stream.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(*volume as f64);
|
||||
let map = output_box_imp.reset_sink_map.read().unwrap();
|
||||
if let Some(index) = map.get(&alias) {
|
||||
imp.reset_sink_selection.set_selected(index.1);
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on input stream change event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
let res = conn.add_match(input_stream_removed, move |ir: InputStreamRemoved, _, _| {
|
||||
let sink_box = input_stream_removed_box.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let output_box = sink_box.clone();
|
||||
let output_box_imp = output_box.imp();
|
||||
let mut list = output_box_imp.reset_input_stream_list.write().unwrap();
|
||||
let entry = list.remove(&ir.index);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
output_box_imp
|
||||
.reset_input_streams
|
||||
.remove(&*entry.unwrap().0);
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on input stream remove event");
|
||||
return conn;
|
||||
}
|
||||
|
||||
conn
|
||||
}
|
94
src/components/output/sink_box_impl.rs
Normal file
94
src/components/output/sink_box_impl.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
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::base::list_entry::ListEntry;
|
||||
use crate::components::output::input_stream_entry::InputStreamEntry;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, Box, Button, CheckButton, CompositeTemplate, Label, StringList, TemplateChild};
|
||||
use gtk::{prelude::*, ProgressBar, Scale};
|
||||
|
||||
use super::sink_box;
|
||||
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, 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_volume_meter: TemplateChild<ProgressBar>,
|
||||
#[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>,
|
||||
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 {
|
||||
fn constructed(&self) {
|
||||
let obj = self.obj();
|
||||
obj.setup_callbacks();
|
||||
}
|
||||
}
|
||||
|
||||
impl ListBoxRowImpl for SinkBox {}
|
||||
|
||||
impl WidgetImpl for SinkBox {}
|
||||
|
||||
impl WindowImpl for SinkBox {}
|
||||
|
||||
impl ApplicationWindowImpl for SinkBox {}
|
154
src/components/output/sink_entry.rs
Normal file
154
src/components/output/sink_entry.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ButtonExt, CheckButtonExt, PreferencesRowExt, RangeExt};
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::Error;
|
||||
use glib::subclass::types::ObjectSubclassIsExt;
|
||||
use glib::{clone, Propagation};
|
||||
use gtk::{gio, CheckButton};
|
||||
use re_set_lib::audio::audio_structures::Sink;
|
||||
|
||||
use super::sink_box::{refresh_default_sink, SinkBox};
|
||||
use super::sink_entry_impl;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct SinkEntry(ObjectSubclass<sink_entry_impl::SinkEntry>)
|
||||
@extends adw::PreferencesGroup, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||
}
|
||||
|
||||
unsafe impl Send for SinkEntry {}
|
||||
unsafe impl Sync for SinkEntry {}
|
||||
|
||||
impl SinkEntry {
|
||||
pub fn new(
|
||||
is_default: bool,
|
||||
check_group: Arc<CheckButton>,
|
||||
stream: Sink,
|
||||
output_box: Arc<SinkBox>,
|
||||
) -> Self {
|
||||
let obj: Self = Object::builder().build();
|
||||
// TODO use event callback for progress bar -> this is the "im speaking" indicator
|
||||
{
|
||||
let imp = obj.imp();
|
||||
imp.reset_sink_name.set_title(stream.alias.clone().as_str());
|
||||
let name = Arc::new(stream.name.clone());
|
||||
let volume = stream.volume.first().unwrap_or(&0_u32);
|
||||
let fraction = (*volume as f64 / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
imp.reset_volume_slider.set_value(*volume as f64);
|
||||
imp.stream.replace(stream);
|
||||
imp.reset_volume_slider.connect_change_value(
|
||||
clone!(@weak imp => @default-return Propagation::Stop, move |_, _, value| {
|
||||
let fraction = (value / 655.36).round();
|
||||
let percentage = (fraction).to_string() + "%";
|
||||
imp.reset_volume_percentage.set_text(&percentage);
|
||||
let sink = imp.stream.borrow();
|
||||
let index = sink.index;
|
||||
let channels = sink.channels;
|
||||
{
|
||||
let mut time = imp.volume_time_stamp.borrow_mut();
|
||||
if time.is_some() && time.unwrap().elapsed().unwrap() < Duration::from_millis(50) {
|
||||
return Propagation::Proceed;
|
||||
}
|
||||
*time = Some(SystemTime::now());
|
||||
}
|
||||
set_sink_volume(value, index, channels);
|
||||
Propagation::Proceed
|
||||
}),
|
||||
);
|
||||
imp.reset_selected_sink.set_group(Some(&*check_group));
|
||||
if is_default {
|
||||
imp.reset_selected_sink.set_active(true);
|
||||
} else {
|
||||
imp.reset_selected_sink.set_active(false);
|
||||
}
|
||||
imp.reset_selected_sink.connect_toggled(move |button| {
|
||||
let output_box_ref = output_box.clone();
|
||||
if button.is_active() {
|
||||
let name = name.clone();
|
||||
gio::spawn_blocking(move || {
|
||||
let result = set_default_sink(name);
|
||||
if result.is_none() {
|
||||
return;
|
||||
}
|
||||
refresh_default_sink(result.unwrap(), output_box_ref, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
imp.reset_sink_mute
|
||||
.connect_clicked(clone!(@weak imp => move |_| {
|
||||
let stream = imp.stream.clone();
|
||||
let mut stream = stream.borrow_mut();
|
||||
stream.muted = !stream.muted;
|
||||
if stream.muted {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-muted-symbolic");
|
||||
} else {
|
||||
imp.reset_sink_mute
|
||||
.set_icon_name("audio-volume-high-symbolic");
|
||||
}
|
||||
toggle_sink_mute(stream.index, stream.muted);
|
||||
}));
|
||||
}
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sink_volume(value: f64, index: u32, channels: u16) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetAudio",
|
||||
"SetSinkVolume",
|
||||
(index, channels, value as u32),
|
||||
);
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
pub fn toggle_sink_mute(index: u32, muted: bool) -> bool {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "SetSinkMute", (index, muted));
|
||||
// if res.is_err() {
|
||||
// return false;
|
||||
// }
|
||||
// res.unwrap().0
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_default_sink(name: Arc<String>) -> Option<Sink> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Sink,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetAudio", "SetDefaultSink", (name.as_str(),));
|
||||
if res.is_err() {
|
||||
return None;
|
||||
}
|
||||
Some(res.unwrap().0)
|
||||
}
|
51
src/components/output/sink_entry_impl.rs
Normal file
51
src/components/output/sink_entry_impl.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
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::output::sink_entry;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, Button, CheckButton, CompositeTemplate, Label, ProgressBar, Scale};
|
||||
|
||||
#[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>,
|
||||
#[template_child]
|
||||
pub reset_volume_meter: TemplateChild<ProgressBar>,
|
||||
pub stream: 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 {}
|
43
src/components/utils.rs
Normal file
43
src/components/utils.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use adw::gdk::pango::EllipsizeMode;
|
||||
use adw::prelude::ListModelExtManual;
|
||||
use adw::ComboRow;
|
||||
use glib::{Cast, Object};
|
||||
use gtk::prelude::{GObjectPropertyExpressionExt, ListBoxRowExt, ListItemExt, WidgetExt};
|
||||
use gtk::{Align, SignalListItemFactory, StringObject};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
src/components/wifi/mod.rs
Normal file
13
src/components/wifi/mod.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
pub mod saved_wifi_entry;
|
||||
pub mod saved_wifi_entry_impl;
|
||||
pub mod utils;
|
||||
pub mod wifi_address_entry;
|
||||
pub mod wifi_address_entry_impl;
|
||||
pub mod wifi_box;
|
||||
pub mod wifi_box_impl;
|
||||
pub mod wifi_entry;
|
||||
pub mod wifi_entry_impl;
|
||||
pub mod wifi_options;
|
||||
pub mod wifi_options_impl;
|
||||
pub mod wifi_route_entry;
|
||||
pub mod wifi_route_entry_impl;
|
61
src/components/wifi/saved_wifi_entry.rs
Normal file
61
src/components/wifi/saved_wifi_entry.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::components::wifi::saved_wifi_entry_impl;
|
||||
use crate::components::wifi::wifi_box_impl::WifiBox;
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ButtonExt, WidgetExt};
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::{Error, Path};
|
||||
use glib::subclass::types::ObjectSubclassIsExt;
|
||||
use glib::{clone, PropertySet};
|
||||
use gtk::gio;
|
||||
use gtk::prelude::ListBoxRowExt;
|
||||
|
||||
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, gtk::ListBoxRow;
|
||||
}
|
||||
|
||||
impl SavedWifiEntry {
|
||||
pub fn new(name: &str, path: Path<'static>, wifi_box: &WifiBox) -> Self {
|
||||
let entry: SavedWifiEntry = Object::builder().build();
|
||||
entry.set_activatable(false);
|
||||
let entry_imp = entry.imp();
|
||||
|
||||
entry_imp.reset_edit_saved_wifi_button.connect_clicked(
|
||||
clone!(@ weak entry_imp, @ weak wifi_box => move |_| {
|
||||
// TODO accesspoint has to be saved somewhere i guess
|
||||
// let _option = getConnectionSettings(entryImp.accessPoint.borrow().associated_connection.clone());
|
||||
// wifiBox.resetWifiNavigation.push(&*WifiOptions::new(_option));
|
||||
}),
|
||||
);
|
||||
|
||||
entry_imp.reset_saved_wifi_label.set_text(name);
|
||||
entry_imp.reset_connection_path.set(path);
|
||||
entry_imp.reset_delete_saved_wifi_button.connect_clicked(
|
||||
clone!(@weak entry as entry => move |_| {
|
||||
delete_connection(entry.imp().reset_connection_path.take());
|
||||
// TODO handle error
|
||||
let parent = entry.parent().unwrap();
|
||||
parent.set_visible(false);
|
||||
parent.unparent();
|
||||
}),
|
||||
);
|
||||
entry
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_connection(path: Path<'static>) {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "DeleteConnection", (path,));
|
||||
});
|
||||
}
|
61
src/components/wifi/saved_wifi_entry_impl.rs
Normal file
61
src/components/wifi/saved_wifi_entry_impl.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
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::{glib, Button, CompositeTemplate, Label};
|
||||
|
||||
use super::saved_wifi_entry;
|
||||
|
||||
#[derive(Default, CompositeTemplate)]
|
||||
#[template(resource = "/org/Xetibo/ReSet/resetSavedWifiEntry.ui")]
|
||||
pub struct SavedWifiEntry {
|
||||
#[template_child]
|
||||
pub reset_delete_saved_wifi_button: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_edit_saved_wifi_button: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_saved_wifi_label: TemplateChild<Label>,
|
||||
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 {}
|
37
src/components/wifi/utils.rs
Normal file
37
src/components/wifi/utils.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
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;
|
||||
|
||||
#[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(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: ResultType =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "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()
|
||||
}
|
160
src/components/wifi/wifi_address_entry.rs
Normal file
160
src/components/wifi/wifi_address_entry.rs
Normal file
|
@ -0,0 +1,160 @@
|
|||
use std::cell::RefCell;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use adw::glib;
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::PreferencesRowExt;
|
||||
use glib::clone;
|
||||
use glib::subclass::prelude::ObjectSubclassIsExt;
|
||||
use gtk::prelude::{ButtonExt, EditableExt, WidgetExt};
|
||||
use re_set_lib::network::connection::{Address, 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_length.to_string());
|
||||
entry_imp
|
||||
.reset_address_row
|
||||
.set_title(&format!("{}/{}", &*address.address, address.prefix_length));
|
||||
}
|
||||
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 as u32));
|
||||
*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_length = 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_length = 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);
|
||||
}
|
52
src/components/wifi/wifi_address_entry_impl.rs
Normal file
52
src/components/wifi/wifi_address_entry_impl.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use crate::components::wifi::utils::IpProtocol;
|
||||
use crate::components::wifi::wifi_address_entry;
|
||||
use adw::{EntryRow, ExpanderRow};
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, 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 {}
|
454
src/components/wifi/wifi_box.rs
Normal file
454
src/components/wifi/wifi_box.rs
Normal file
|
@ -0,0 +1,454 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::components::base::utils::Listeners;
|
||||
use crate::components::utils::set_combo_row_ellipsis;
|
||||
use adw::glib;
|
||||
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::{clone, Cast, PropertySet};
|
||||
use gtk::glib::Variant;
|
||||
use gtk::prelude::{ActionableExt, WidgetExt};
|
||||
use gtk::{gio, StringObject};
|
||||
use re_set_lib::network::network_structures::{AccessPoint, WifiDevice, WifiStrength};
|
||||
use re_set_lib::signals::{AccessPointAdded, WifiDeviceChanged};
|
||||
use re_set_lib::signals::{AccessPointChanged, AccessPointRemoved};
|
||||
|
||||
use crate::components::wifi::wifi_box_impl;
|
||||
use crate::components::wifi::wifi_entry::WifiEntry;
|
||||
|
||||
use super::saved_wifi_entry::SavedWifiEntry;
|
||||
|
||||
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 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 wifi_status = get_wifi_status();
|
||||
let imp = wifi_box.imp();
|
||||
let wifibox_ref = wifi_box.clone();
|
||||
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_wifi_switch.set_active(wifi_status);
|
||||
imp.reset_wifi_switch.set_state(wifi_status);
|
||||
|
||||
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| {
|
||||
set_wifi_enabled(value);
|
||||
if !value {
|
||||
let mut map = imp.wifi_entries.lock().unwrap();
|
||||
for entry in map.iter() {
|
||||
imp.reset_wifi_list.remove(&*(*entry.1));
|
||||
}
|
||||
map.clear();
|
||||
imp.wifi_entries_path.lock().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 _wifibox_ref_listener = wifi_box.clone();
|
||||
let wifi_entries = wifi_box.imp().wifi_entries.clone();
|
||||
let wifi_entries_path = wifi_box.imp().wifi_entries_path.clone();
|
||||
|
||||
gio::spawn_blocking(move || {
|
||||
let access_points = get_access_points();
|
||||
let devices = get_wifi_devices();
|
||||
{
|
||||
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();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let mut wifi_entries = wifi_entries.lock().unwrap();
|
||||
let mut wifi_entries_path = wifi_entries_path.lock().unwrap();
|
||||
let imp = wifibox_ref.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);
|
||||
}
|
||||
}
|
||||
|
||||
imp.reset_wifi_device.connect_selected_notify(
|
||||
clone!(@weak imp => move |dropdown| {
|
||||
let selected = dropdown.selected_item();
|
||||
if selected.is_none() {
|
||||
return;
|
||||
}
|
||||
let selected = selected.unwrap();
|
||||
let selected = selected.downcast_ref::<StringObject>().unwrap();
|
||||
let selected = selected.string().to_string();
|
||||
|
||||
let device = imp.reset_wifi_devices.read().unwrap();
|
||||
let device = device.get(&selected);
|
||||
if device.is_none() {
|
||||
return;
|
||||
}
|
||||
set_wifi_device(device.unwrap().0.path.clone());
|
||||
}),
|
||||
);
|
||||
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 == path;
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn show_stored_connections(wifi_box: Arc<WifiBox>) {
|
||||
let wifibox_ref = wifi_box.clone();
|
||||
gio::spawn_blocking(move || {
|
||||
let connections = get_stored_connections();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let self_imp = wifibox_ref.imp();
|
||||
for connection in connections {
|
||||
// TODO include button for settings
|
||||
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() {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "StartNetworkListener", ());
|
||||
}
|
||||
|
||||
pub fn get_access_points() -> Vec<AccessPoint> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<AccessPoint>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "ListAccessPoints", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
let (access_points,) = res.unwrap();
|
||||
access_points
|
||||
}
|
||||
|
||||
pub fn set_wifi_device(path: Path<'static>) {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(bool,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "SetWifiDevice", (path,));
|
||||
}
|
||||
|
||||
pub fn get_wifi_devices() -> Vec<WifiDevice> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(Vec<WifiDevice>,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "GetAllWifiDevices", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
let (devices,) = res.unwrap();
|
||||
devices
|
||||
}
|
||||
|
||||
pub fn get_wifi_status() -> bool {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: Result<(bool,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "GetWifiStatus", ());
|
||||
if res.is_err() {
|
||||
return false;
|
||||
}
|
||||
res.unwrap().0
|
||||
}
|
||||
|
||||
pub fn get_stored_connections() -> Vec<(Path<'static>, Vec<u8>)> {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let res: ResultMap = proxy.method_call("org.Xetibo.ReSetWireless", "ListStoredConnections", ());
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
let (connections,) = res.unwrap();
|
||||
connections
|
||||
}
|
||||
|
||||
pub fn set_wifi_enabled(enabled: bool) {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(bool,), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetWireless", "SetWifiEnabled", (enabled,));
|
||||
}
|
||||
|
||||
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 access_point_added = AccessPointAdded::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let access_point_removed = AccessPointRemoved::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let access_point_changed = AccessPointChanged::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let device_changed = WifiDeviceChanged::match_rule(
|
||||
Some(&"org.Xetibo.ReSetDaemon".into()),
|
||||
Some(&Path::from("/org/Xetibo/ReSetDaemon")),
|
||||
)
|
||||
.static_clone();
|
||||
let res = conn.add_match(access_point_added, move |ir: AccessPointAdded, _, _| {
|
||||
let wifi_box = added_ref.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = wifi_box.imp();
|
||||
let mut wifi_entries = imp.wifi_entries.lock().unwrap();
|
||||
let mut wifi_entries_path = imp.wifi_entries_path.lock().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.dbus_path;
|
||||
let entry = WifiEntry::new(connected, ir.access_point, imp);
|
||||
wifi_entries.insert(ssid, entry.clone());
|
||||
wifi_entries_path.insert(path, entry.clone());
|
||||
imp.reset_wifi_list.add(&*entry);
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on access point add event");
|
||||
return;
|
||||
}
|
||||
let res = conn.add_match(access_point_removed, move |ir: AccessPointRemoved, _, _| {
|
||||
let wifi_box = removed_ref.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = wifi_box.imp();
|
||||
let mut wifi_entries = imp.wifi_entries.lock().unwrap();
|
||||
let mut wifi_entries_path = imp.wifi_entries_path.lock().unwrap();
|
||||
let entry = wifi_entries_path.remove(&ir.access_point);
|
||||
if entry.is_none() {
|
||||
return;
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
let ssid = entry.imp().access_point.borrow().ssid.clone();
|
||||
wifi_entries.remove(&ssid);
|
||||
imp.reset_wifi_list.remove(&*entry);
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on access point remove event");
|
||||
return;
|
||||
}
|
||||
let res = conn.add_match(access_point_changed, move |ir: AccessPointChanged, _, _| {
|
||||
let wifi_box = changed_ref.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_local_once(move || {
|
||||
let imp = wifi_box.imp();
|
||||
let wifi_entries = imp.wifi_entries.lock().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_imp.reset_wifi_label.get().set_text(name);
|
||||
entry_imp.reset_wifi_encrypted.set_visible(false);
|
||||
// TODO handle encryption thing
|
||||
entry_imp
|
||||
.reset_wifi_strength
|
||||
.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"),
|
||||
});
|
||||
if !ir.access_point.stored {
|
||||
entry_imp.reset_wifi_edit_button.set_sensitive(false);
|
||||
}
|
||||
if ir.access_point.dbus_path
|
||||
== imp.reset_current_wifi_device.borrow().active_access_point
|
||||
{
|
||||
entry_imp.reset_wifi_connected.set_text("Connected");
|
||||
} else {
|
||||
entry_imp.reset_wifi_connected.set_text("");
|
||||
}
|
||||
{
|
||||
let mut wifi_name = entry_imp.wifi_name.borrow_mut();
|
||||
*wifi_name = String::from(name);
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on access point change event");
|
||||
return;
|
||||
}
|
||||
let res = conn.add_match(device_changed, move |ir: WifiDeviceChanged, _, _| {
|
||||
let wifi_box = wifi_changed_ref.clone();
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
let imp = wifi_box.imp();
|
||||
let mut current_device = imp.reset_current_wifi_device.borrow_mut();
|
||||
if current_device.path == ir.wifi_device.path {
|
||||
current_device.active_access_point = ir.wifi_device.active_access_point;
|
||||
} else {
|
||||
*current_device = ir.wifi_device;
|
||||
}
|
||||
let mut wifi_entries = imp.wifi_entries.lock().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().dbus_path == current_device.path;
|
||||
if *connected {
|
||||
imp.reset_wifi_connected.set_text("Connected");
|
||||
} else {
|
||||
imp.reset_wifi_connected.set_text("");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
true
|
||||
});
|
||||
if res.is_err() {
|
||||
println!("fail on wifi device change event");
|
||||
return;
|
||||
}
|
||||
|
||||
loop {
|
||||
let _ = conn.process(Duration::from_millis(1000));
|
||||
if !listeners.wifi_listener.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
79
src/components/wifi/wifi_box_impl.rs
Normal file
79
src/components/wifi/wifi_box_impl.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use crate::components::wifi::wifi_box;
|
||||
use adw::{ActionRow, ComboRow, NavigationView, PreferencesGroup};
|
||||
use dbus::Path;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, CompositeTemplate, Switch};
|
||||
use gtk::{prelude::*, StringList};
|
||||
use re_set_lib::network::network_structures::WifiDevice;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, 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>,
|
||||
pub wifi_entries: Arc<Mutex<HashMap<Vec<u8>, Arc<WifiEntry>>>>,
|
||||
pub wifi_entries_path: Arc<Mutex<HashMap<Path<'static>, Arc<WifiEntry>>>>,
|
||||
pub saved_wifi_entries: Arc<Mutex<Vec<ListEntry>>>,
|
||||
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>>,
|
||||
}
|
||||
|
||||
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 {}
|
224
src/components/wifi/wifi_entry.rs
Normal file
224
src/components/wifi/wifi_entry.rs
Normal file
|
@ -0,0 +1,224 @@
|
|||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::components::wifi::utils::get_connection_settings;
|
||||
use adw::glib;
|
||||
use adw::glib::{Object, PropertySet};
|
||||
use adw::prelude::{ActionRowExt, ButtonExt, EditableExt, PopoverExt};
|
||||
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::Error;
|
||||
use glib::clone;
|
||||
use gtk::gio;
|
||||
use gtk::prelude::{ListBoxRowExt, WidgetExt};
|
||||
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, 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_imp.reset_wifi_label.get().set_text(name);
|
||||
entry_imp.reset_wifi_encrypted.set_visible(false);
|
||||
entry_imp.connected.set(connected);
|
||||
// TODO handle encryption thing
|
||||
entry_imp
|
||||
.reset_wifi_strength
|
||||
.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"),
|
||||
});
|
||||
if !access_point.stored {
|
||||
entry_imp.reset_wifi_edit_button.set_sensitive(false);
|
||||
}
|
||||
if connected {
|
||||
entry_imp.reset_wifi_connected.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.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().dbus_path.clone()));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn click_disconnect(entry: Arc<WifiEntry>) {
|
||||
let entry_ref = entry.clone();
|
||||
entry.set_activatable(false);
|
||||
gio::spawn_blocking(move || {
|
||||
let imp = entry_ref.imp();
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(10000),
|
||||
);
|
||||
let res: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetWireless",
|
||||
"DisconnectFromCurrentAccessPoint",
|
||||
(),
|
||||
);
|
||||
if res.is_err() {
|
||||
imp.connected.replace(false);
|
||||
return;
|
||||
}
|
||||
imp.reset_wifi_connected.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_activatable(false);
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(10000),
|
||||
);
|
||||
let res: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetWireless",
|
||||
"ConnectToKnownAccessPoint",
|
||||
(access_point,),
|
||||
);
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
entry.set_activatable(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.set_text("Connected");
|
||||
imp.connected.replace(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
// 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);
|
||||
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(10000),
|
||||
);
|
||||
let res: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetWireless",
|
||||
"ConnectToNewAccessPoint",
|
||||
(access_point, password),
|
||||
);
|
||||
glib::spawn_future(async move {
|
||||
glib::idle_add_once(move || {
|
||||
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.set_sensitive(true);
|
||||
imp.reset_wifi_connected.set_text("Connected");
|
||||
imp.connected.replace(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
// 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();
|
||||
}
|
67
src/components/wifi/wifi_entry_impl.rs
Normal file
67
src/components/wifi/wifi_entry_impl.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
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::{glib, 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_strength: TemplateChild<Image>,
|
||||
#[template_child]
|
||||
pub reset_wifi_encrypted: TemplateChild<Image>,
|
||||
#[template_child]
|
||||
pub reset_wifi_label: TemplateChild<Label>,
|
||||
#[template_child]
|
||||
pub reset_wifi_edit_button: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_wifi_connected: TemplateChild<Label>,
|
||||
#[template_child]
|
||||
pub reset_wifi_popup: TemplateChild<Popup>,
|
||||
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 {}
|
392
src/components/wifi/wifi_options.rs
Normal file
392
src/components/wifi/wifi_options.rs
Normal file
|
@ -0,0 +1,392 @@
|
|||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use adw::glib::Object;
|
||||
use adw::prelude::{ActionRowExt, ComboRowExt, PreferencesGroupExt};
|
||||
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||
use adw::{gio, glib};
|
||||
use dbus::arg::PropMap;
|
||||
use dbus::{Error, Path};
|
||||
use glib::{clone, PropertySet};
|
||||
use gtk::prelude::{ButtonExt, EditableExt, WidgetExt};
|
||||
use re_set_lib::network::connection::{Connection, DNSMethod4, DNSMethod6, Enum, TypeSettings};
|
||||
use IpProtocol::{IPv4, IPv6};
|
||||
|
||||
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, access_point: 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, access_point);
|
||||
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 != -1);
|
||||
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(ðernet.cloned_mac_address);
|
||||
self_imp
|
||||
.reset_wifi_link_speed
|
||||
.set_subtitle(ðernet.speed.to_string());
|
||||
}
|
||||
TypeSettings::VPN(_vpn) => {}
|
||||
TypeSettings::None => {}
|
||||
};
|
||||
// IPv4
|
||||
self_imp
|
||||
.reset_ip4_method
|
||||
.set_selected(conn.ipv4.dns_method.to_i32() as u32);
|
||||
self.set_ip4_visibility(conn.ipv4.dns_method.to_i32() as u32);
|
||||
|
||||
let ipv4_dns: Vec<String> = conn
|
||||
.ipv4
|
||||
.dns
|
||||
.iter()
|
||||
.map(|addr| {
|
||||
addr.iter()
|
||||
.map(|octet| octet.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(".")
|
||||
})
|
||||
.collect();
|
||||
self_imp.reset_ip4_dns.set_text(&ipv4_dns.join(", "));
|
||||
self_imp.reset_ip4_gateway.set_text(&conn.ipv4.gateway);
|
||||
// IPv6
|
||||
self_imp
|
||||
.reset_ip6_method
|
||||
.set_selected(conn.ipv6.dns_method.to_i32() as u32);
|
||||
self.set_ip6_visibility(conn.ipv6.dns_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
|
||||
if let TypeSettings::WIFI(wifi) = &conn.device {
|
||||
match wifi.security_settings.key_management.as_str() {
|
||||
"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("");
|
||||
}
|
||||
"wpa-psk" => {
|
||||
self_imp.reset_wifi_security_dropdown.set_selected(1);
|
||||
self_imp.reset_wifi_password.set_visible(true);
|
||||
self_imp
|
||||
.reset_wifi_password
|
||||
.set_text(&wifi.security_settings.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);
|
||||
// Security
|
||||
}
|
||||
|
||||
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.dns_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(addr.octets().to_vec());
|
||||
} 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(), IpProtocol::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.dns_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(), IpProtocol::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, &mut conn.device) {
|
||||
(0 , TypeSettings::WIFI(wifi)) => { // None
|
||||
imp.reset_wifi_password.set_visible(false);
|
||||
wifi.security_settings.key_management = String::from("none");
|
||||
wifi.security_settings.authentication_algorithm = String::from("open");
|
||||
},
|
||||
(1 , TypeSettings::WIFI(wifi)) => { // WPA/WPA2 Personal
|
||||
imp.reset_wifi_password.set_visible(true);
|
||||
wifi.security_settings.key_management = String::from("wpa-psk");
|
||||
wifi.security_settings.authentication_algorithm = String::from("");
|
||||
},
|
||||
(_, _) => {}
|
||||
}
|
||||
}));
|
||||
|
||||
imp.reset_wifi_password
|
||||
.connect_changed(clone!(@weak imp => move |entry| {
|
||||
let password_input = entry.text();
|
||||
let mut conn = imp.connection.borrow_mut();
|
||||
if let TypeSettings::WIFI(wifi) = &mut conn.device {
|
||||
wifi.security_settings.psk = password_input.to_string();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
fn set_connection_settings(path: Path<'static>, prop: PropMap) {
|
||||
gio::spawn_blocking(move || {
|
||||
let conn = dbus::blocking::Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
let _: Result<(bool,), Error> = proxy.method_call(
|
||||
"org.Xetibo.ReSetWireless",
|
||||
"SetConnectionSettings",
|
||||
(path, prop),
|
||||
);
|
||||
});
|
||||
}
|
107
src/components/wifi/wifi_options_impl.rs
Normal file
107
src/components/wifi/wifi_options_impl.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
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::{glib, Button, CompositeTemplate};
|
||||
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 wifi_options_apply_button: TemplateChild<Button>,
|
||||
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 {}
|
242
src/components/wifi/wifi_route_entry.rs
Normal file
242
src/components/wifi/wifi_route_entry.rs
Normal file
|
@ -0,0 +1,242 @@
|
|||
use crate::components::wifi::utils::IpProtocol;
|
||||
use adw::glib;
|
||||
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_length.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_length));
|
||||
}
|
||||
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_length = 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_length = 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);
|
||||
}
|
58
src/components/wifi/wifi_route_entry_impl.rs
Normal file
58
src/components/wifi/wifi_route_entry_impl.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use crate::components::wifi::utils::IpProtocol;
|
||||
use crate::components::wifi::wifi_route_entry;
|
||||
use adw::{EntryRow, ExpanderRow};
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, 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 {}
|
221
src/components/window/handle_sidebar_click.rs
Normal file
221
src/components/window/handle_sidebar_click.rs
Normal file
|
@ -0,0 +1,221 @@
|
|||
use gtk::prelude::FrameExt;
|
||||
use std::cell::RefCell;
|
||||
use std::hint::spin_loop;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::components::base::setting_box::SettingBox;
|
||||
use crate::components::base::utils::{start_audio_listener, Listeners, Position};
|
||||
use crate::components::bluetooth::bluetooth_box::{
|
||||
populate_conntected_bluetooth_devices, start_bluetooth_listener, BluetoothBox,
|
||||
};
|
||||
use crate::components::input::source_box::{populate_sources, SourceBox};
|
||||
use crate::components::output::sink_box::{populate_sinks, SinkBox};
|
||||
use crate::components::wifi::wifi_box::{
|
||||
scan_for_wifi, show_stored_connections, start_event_listener, WifiBox,
|
||||
};
|
||||
use gtk::prelude::WidgetExt;
|
||||
use gtk::{Align, FlowBox, FlowBoxChild, Frame};
|
||||
|
||||
pub const HANDLE_CONNECTIVITY_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|
||||
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
|
||||
if handle_init(listeners.clone(), position, Position::Connectivity) {
|
||||
return;
|
||||
}
|
||||
let wifi_box = WifiBox::new(listeners.clone());
|
||||
start_event_listener(listeners.clone(), wifi_box.clone());
|
||||
show_stored_connections(wifi_box.clone());
|
||||
scan_for_wifi(wifi_box.clone());
|
||||
let wifi_frame = wrap_in_flow_box_child(SettingBox::new(&*wifi_box));
|
||||
let bluetooth_box = BluetoothBox::new(listeners.clone());
|
||||
populate_conntected_bluetooth_devices(bluetooth_box.clone());
|
||||
start_bluetooth_listener(listeners, bluetooth_box.clone());
|
||||
let bluetooth_frame = wrap_in_flow_box_child(SettingBox::new(&*bluetooth_box));
|
||||
reset_main.remove_all();
|
||||
reset_main.insert(&wifi_frame, -1);
|
||||
reset_main.insert(&bluetooth_frame, -1);
|
||||
reset_main.set_max_children_per_line(2);
|
||||
};
|
||||
|
||||
pub const HANDLE_WIFI_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|
||||
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
|
||||
if handle_init(listeners.clone(), position, Position::Wifi) {
|
||||
return;
|
||||
}
|
||||
let wifi_box = WifiBox::new(listeners.clone());
|
||||
start_event_listener(listeners, wifi_box.clone());
|
||||
show_stored_connections(wifi_box.clone());
|
||||
scan_for_wifi(wifi_box.clone());
|
||||
let wifi_frame = wrap_in_flow_box_child(SettingBox::new(&*wifi_box));
|
||||
reset_main.remove_all();
|
||||
reset_main.insert(&wifi_frame, -1);
|
||||
reset_main.set_max_children_per_line(1);
|
||||
};
|
||||
|
||||
pub const HANDLE_BLUETOOTH_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|
||||
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
|
||||
if handle_init(listeners.clone(), position, Position::Bluetooth) {
|
||||
return;
|
||||
}
|
||||
let bluetooth_box = BluetoothBox::new(listeners.clone());
|
||||
start_bluetooth_listener(listeners, bluetooth_box.clone());
|
||||
populate_conntected_bluetooth_devices(bluetooth_box.clone());
|
||||
let bluetooth_frame = wrap_in_flow_box_child(SettingBox::new(&*bluetooth_box));
|
||||
reset_main.remove_all();
|
||||
reset_main.insert(&bluetooth_frame, -1);
|
||||
reset_main.set_max_children_per_line(1);
|
||||
};
|
||||
|
||||
pub const HANDLE_AUDIO_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|
||||
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
|
||||
if handle_init(listeners.clone(), position, Position::Audio) {
|
||||
return;
|
||||
}
|
||||
let audio_output = Arc::new(SinkBox::new());
|
||||
let audio_input = Arc::new(SourceBox::new());
|
||||
start_audio_listener(
|
||||
listeners.clone(),
|
||||
Some(audio_output.clone()),
|
||||
Some(audio_input.clone()),
|
||||
);
|
||||
if !listeners.pulse_listener.load(Ordering::SeqCst) {
|
||||
spin_loop();
|
||||
}
|
||||
populate_sinks(audio_output.clone());
|
||||
populate_sources(audio_input.clone());
|
||||
let sink_frame = wrap_in_flow_box_child(SettingBox::new(&*audio_output));
|
||||
let source_frame = wrap_in_flow_box_child(SettingBox::new(&*audio_input));
|
||||
reset_main.remove_all();
|
||||
reset_main.insert(&sink_frame, -1);
|
||||
reset_main.insert(&source_frame, -1);
|
||||
reset_main.set_max_children_per_line(2);
|
||||
};
|
||||
|
||||
pub const HANDLE_VOLUME_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|
||||
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
|
||||
if handle_init(listeners.clone(), position, Position::AudioOutput) {
|
||||
return;
|
||||
}
|
||||
let audio_output = Arc::new(SinkBox::new());
|
||||
start_audio_listener(listeners.clone(), Some(audio_output.clone()), None);
|
||||
if !listeners.pulse_listener.load(Ordering::SeqCst) {
|
||||
spin_loop();
|
||||
}
|
||||
populate_sinks(audio_output.clone());
|
||||
let audio_frame = wrap_in_flow_box_child(SettingBox::new(&*audio_output));
|
||||
reset_main.remove_all();
|
||||
reset_main.insert(&audio_frame, -1);
|
||||
reset_main.set_max_children_per_line(1);
|
||||
};
|
||||
|
||||
pub const HANDLE_MICROPHONE_CLICK: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|
||||
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
|
||||
if handle_init(listeners.clone(), position, Position::AudioInput) {
|
||||
return;
|
||||
}
|
||||
let audio_input = Arc::new(SourceBox::new());
|
||||
start_audio_listener(listeners.clone(), None, Some(audio_input.clone()));
|
||||
if !listeners.pulse_listener.load(Ordering::SeqCst) {
|
||||
spin_loop();
|
||||
}
|
||||
populate_sources(audio_input.clone());
|
||||
let source_frame = wrap_in_flow_box_child(SettingBox::new(&*audio_input));
|
||||
reset_main.remove_all();
|
||||
reset_main.insert(&source_frame, -1);
|
||||
reset_main.set_max_children_per_line(1);
|
||||
};
|
||||
|
||||
pub const HANDLE_HOME: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>) =
|
||||
|listeners: Arc<Listeners>, reset_main: FlowBox, position: Rc<RefCell<Position>>| {
|
||||
if handle_init(listeners, position, Position::Home) {
|
||||
return;
|
||||
}
|
||||
reset_main.remove_all();
|
||||
};
|
||||
|
||||
fn wrap_in_flow_box_child(widget: SettingBox) -> FlowBoxChild {
|
||||
let frame = Frame::new(None);
|
||||
frame.set_child(Some(&widget));
|
||||
frame.add_css_class("resetSettingFrame");
|
||||
FlowBoxChild::builder()
|
||||
.child(&frame)
|
||||
.halign(Align::Fill)
|
||||
.valign(Align::Start)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn handle_init(
|
||||
listeners: Arc<Listeners>,
|
||||
position: Rc<RefCell<Position>>,
|
||||
clicked_position: Position,
|
||||
) -> bool {
|
||||
{
|
||||
let mut pos_borrow = position.borrow_mut();
|
||||
if *pos_borrow == clicked_position {
|
||||
return true;
|
||||
}
|
||||
*pos_borrow = clicked_position;
|
||||
}
|
||||
listeners.stop_network_listener();
|
||||
listeners.stop_audio_listener();
|
||||
listeners.stop_bluetooth_listener();
|
||||
false
|
||||
}
|
||||
|
||||
// for future implementations
|
||||
// pub const HANDLE_VPN_CLICK: fn(Arc<Listeners>, FlowBox) =
|
||||
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
|
||||
// listeners.stop_network_listener();
|
||||
// listeners.stop_bluetooth_listener();
|
||||
// listeners.stop_audio_listener();
|
||||
// let label = Label::new(Some("not implemented yet"));
|
||||
// resetMain.remove_all();
|
||||
// resetMain.insert(&label, -1);
|
||||
// resetMain.set_max_children_per_line(1);
|
||||
// };
|
||||
//
|
||||
// pub const HANDLE_PERIPHERALS_CLICK: fn(Arc<Listeners>, FlowBox) =
|
||||
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
|
||||
// listeners.stop_network_listener();
|
||||
// listeners.stop_audio_listener();
|
||||
// listeners.stop_bluetooth_listener();
|
||||
// let label = Label::new(Some("not implemented yet"));
|
||||
// resetMain.remove_all();
|
||||
// resetMain.insert(&label, -1);
|
||||
// resetMain.set_max_children_per_line(1);
|
||||
// };
|
||||
//
|
||||
// pub const HANDLE_MONITOR_CLICK: fn(Arc<Listeners>, FlowBox) =
|
||||
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
|
||||
// listeners.stop_network_listener();
|
||||
// listeners.stop_audio_listener();
|
||||
// listeners.stop_bluetooth_listener();
|
||||
// let label = Label::new(Some("not implemented yet"));
|
||||
// resetMain.remove_all();
|
||||
// resetMain.insert(&label, -1);
|
||||
// resetMain.set_max_children_per_line(1);
|
||||
// };
|
||||
//
|
||||
// pub const HANDLE_MOUSE_CLICK: fn(Arc<Listeners>, FlowBox) =
|
||||
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
|
||||
// listeners.stop_network_listener();
|
||||
// listeners.stop_audio_listener();
|
||||
// listeners.stop_bluetooth_listener();
|
||||
// let label = Label::new(Some("not implemented yet"));
|
||||
// resetMain.remove_all();
|
||||
// resetMain.insert(&label, -1);
|
||||
// resetMain.set_max_children_per_line(1);
|
||||
// };
|
||||
//
|
||||
// pub const HANDLE_KEYBOARD_CLICK: fn(Arc<Listeners>, FlowBox) =
|
||||
// |listeners: Arc<Listeners>, resetMain: FlowBox| {
|
||||
// listeners.stop_network_listener();
|
||||
// listeners.stop_audio_listener();
|
||||
// listeners.stop_bluetooth_listener();
|
||||
// let label = Label::new(Some("not implemented yet"));
|
||||
// resetMain.remove_all();
|
||||
// resetMain.insert(&label, -1);
|
||||
// resetMain.set_max_children_per_line(1);
|
||||
// };
|
||||
//
|
5
src/components/window/mod.rs
Normal file
5
src/components/window/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod handle_sidebar_click;
|
||||
pub mod reset_window;
|
||||
pub mod reset_window_impl;
|
||||
pub mod sidebar_entry;
|
||||
pub mod sidebar_entry_impl;
|
293
src/components/window/reset_window.rs
Normal file
293
src/components/window/reset_window.rs
Normal file
|
@ -0,0 +1,293 @@
|
|||
use adw::glib::clone;
|
||||
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||
use adw::BreakpointCondition;
|
||||
use glib::Object;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{gio, glib, Application, ListBoxRow, Orientation};
|
||||
|
||||
use crate::components::window::handle_sidebar_click::*;
|
||||
use crate::components::window::reset_window_impl;
|
||||
use crate::components::window::sidebar_entry::SidebarEntry;
|
||||
use crate::components::window::sidebar_entry_impl::Categories;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ReSetWindow(ObjectSubclass<reset_window_impl::ReSetWindow>)
|
||||
@extends adw::ApplicationWindow, gtk::Window, gtk::Widget,
|
||||
@implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
|
||||
gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
|
||||
}
|
||||
|
||||
unsafe impl Send for ReSetWindow {}
|
||||
|
||||
unsafe impl Sync for ReSetWindow {}
|
||||
|
||||
impl ReSetWindow {
|
||||
pub fn new(app: &Application) -> Self {
|
||||
Object::builder().property("application", app).build()
|
||||
}
|
||||
|
||||
pub fn setup_callback(&self) {
|
||||
let self_imp = self.imp();
|
||||
|
||||
self_imp.reset_search_entry.connect_search_changed(
|
||||
clone!(@ weak self as window => move |_| {
|
||||
window.filter_list();
|
||||
}),
|
||||
);
|
||||
|
||||
self_imp
|
||||
.reset_sidebar_toggle
|
||||
.connect_clicked(clone!(@ weak self as window => move |_| {
|
||||
window.toggle_sidebar();
|
||||
}));
|
||||
|
||||
self_imp.reset_sidebar_list.connect_row_activated(
|
||||
clone!(@ weak self_imp as flowbox => move |_, y| {
|
||||
let result = y.downcast_ref::<SidebarEntry>().unwrap();
|
||||
let click_event = result.imp().on_click_event.borrow().on_click_event;
|
||||
(click_event)(flowbox.listeners.clone(), flowbox.reset_main.get(), flowbox.position.clone());
|
||||
}),
|
||||
);
|
||||
|
||||
self_imp
|
||||
.reset_close
|
||||
.connect_clicked(clone!(@ weak self as window => move |_| {
|
||||
window.close();
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn handle_dynamic_sidebar(&self) {
|
||||
let self_imp = self.imp();
|
||||
self_imp
|
||||
.reset_sidebar_breakpoint
|
||||
.set_condition(BreakpointCondition::parse("max-width: 860sp").as_ref().ok());
|
||||
self_imp.reset_sidebar_breakpoint.add_setter(
|
||||
&Object::from(self_imp.reset_overlay_split_view.get()),
|
||||
"collapsed",
|
||||
&true.to_value(),
|
||||
);
|
||||
self_imp.reset_sidebar_breakpoint.add_setter(
|
||||
&Object::from(self_imp.reset_sidebar_toggle.get()),
|
||||
"visible",
|
||||
&true.to_value(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn filter_list(&self) {
|
||||
let text = self.imp().reset_search_entry.text().to_string();
|
||||
for (main_entry, sub_entriess) in self.imp().sidebar_entries.borrow().iter() {
|
||||
if text.is_empty() {
|
||||
main_entry.set_visible(true);
|
||||
for sub_entry in sub_entriess {
|
||||
sub_entry.set_visible(true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if main_entry
|
||||
.imp()
|
||||
.name
|
||||
.borrow()
|
||||
.to_lowercase()
|
||||
.contains(&text.to_lowercase())
|
||||
{
|
||||
main_entry.set_visible(true);
|
||||
} else {
|
||||
main_entry.set_visible(false);
|
||||
}
|
||||
for sub_entry in sub_entriess {
|
||||
if sub_entry
|
||||
.imp()
|
||||
.name
|
||||
.borrow()
|
||||
.to_lowercase()
|
||||
.contains(&text.to_lowercase())
|
||||
{
|
||||
sub_entry.set_visible(true);
|
||||
main_entry.set_visible(true);
|
||||
} else {
|
||||
sub_entry.set_visible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_sidebar(&self) {
|
||||
if self.imp().reset_overlay_split_view.shows_sidebar() {
|
||||
self.imp().reset_overlay_split_view.set_show_sidebar(false);
|
||||
} else {
|
||||
self.imp().reset_overlay_split_view.set_show_sidebar(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_sidebar_entries(&self) {
|
||||
let self_imp = self.imp();
|
||||
let mut sidebar_entries = self_imp.sidebar_entries.borrow_mut();
|
||||
|
||||
let connectivity_list = vec![
|
||||
SidebarEntry::new(
|
||||
"WiFi",
|
||||
"network-wireless-symbolic",
|
||||
Categories::Connectivity,
|
||||
true,
|
||||
HANDLE_WIFI_CLICK,
|
||||
),
|
||||
SidebarEntry::new(
|
||||
"Bluetooth",
|
||||
"bluetooth-symbolic",
|
||||
Categories::Connectivity,
|
||||
true,
|
||||
HANDLE_BLUETOOTH_CLICK,
|
||||
),
|
||||
// uncommented when VPN is implemented
|
||||
// SidebarEntry::new(
|
||||
// "VPN",
|
||||
// "network-vpn-symbolic",
|
||||
// Categories::Connectivity,
|
||||
// true,
|
||||
// HANDLE_VPN_CLICK,
|
||||
// ),
|
||||
];
|
||||
|
||||
sidebar_entries.push((
|
||||
SidebarEntry::new(
|
||||
"Connectivity",
|
||||
"network-wired-symbolic",
|
||||
Categories::Connectivity,
|
||||
false,
|
||||
HANDLE_CONNECTIVITY_CLICK,
|
||||
),
|
||||
connectivity_list,
|
||||
));
|
||||
|
||||
let audio_list = vec![
|
||||
SidebarEntry::new(
|
||||
"Output",
|
||||
"audio-volume-high-symbolic",
|
||||
Categories::Audio,
|
||||
true,
|
||||
HANDLE_VOLUME_CLICK,
|
||||
),
|
||||
SidebarEntry::new(
|
||||
"Input",
|
||||
"audio-input-microphone-symbolic",
|
||||
Categories::Audio,
|
||||
true,
|
||||
HANDLE_MICROPHONE_CLICK,
|
||||
),
|
||||
];
|
||||
|
||||
sidebar_entries.push((
|
||||
SidebarEntry::new(
|
||||
"Audio",
|
||||
"audio-headset-symbolic",
|
||||
Categories::Audio,
|
||||
false,
|
||||
HANDLE_AUDIO_CLICK,
|
||||
),
|
||||
audio_list,
|
||||
));
|
||||
|
||||
// uncommented when implemented
|
||||
// let peripheralsList = vec![
|
||||
// SidebarEntry::new(
|
||||
// "Displays",
|
||||
// "video-display-symbolic",
|
||||
// Categories::Peripherals,
|
||||
// true,
|
||||
// HANDLE_MONITOR_CLICK,
|
||||
// ),
|
||||
// SidebarEntry::new(
|
||||
// "Mouse",
|
||||
// "input-mouse-symbolic",
|
||||
// Categories::Peripherals,
|
||||
// true,
|
||||
// HANDLE_MOUSE_CLICK,
|
||||
// ),
|
||||
// SidebarEntry::new(
|
||||
// "Keyboard",
|
||||
// "input-keyboard-symbolic",
|
||||
// Categories::Peripherals,
|
||||
// true,
|
||||
// HANDLE_KEYBOARD_CLICK,
|
||||
// ),
|
||||
// ];
|
||||
|
||||
// let home = SidebarEntry::new(
|
||||
// "Home",
|
||||
// "preferences-system-devices-symbolic",
|
||||
// Categories::Peripherals,
|
||||
// false,
|
||||
// HANDLE_VOLUME_CLICK,
|
||||
// );
|
||||
//
|
||||
// sidebar_entries.push((home, Vec::new()));
|
||||
|
||||
(HANDLE_VOLUME_CLICK)(
|
||||
self_imp.listeners.clone(),
|
||||
self_imp.reset_main.clone(),
|
||||
self_imp.position.clone(),
|
||||
);
|
||||
|
||||
self_imp
|
||||
.reset_sidebar_list
|
||||
.connect_row_activated(clone!(@ weak self_imp => move |_, _| {
|
||||
self_imp.reset_search_entry.set_text("");
|
||||
}));
|
||||
|
||||
for (main_entry, sub_entries) in sidebar_entries.iter() {
|
||||
self_imp.reset_sidebar_list.append(main_entry);
|
||||
for sub_entry in sub_entries {
|
||||
self_imp.reset_sidebar_list.append(sub_entry);
|
||||
}
|
||||
let separator = gtk::Separator::builder()
|
||||
.margin_bottom(3)
|
||||
.margin_top(3)
|
||||
.orientation(Orientation::Horizontal)
|
||||
.build();
|
||||
let separator_row = ListBoxRow::new();
|
||||
separator_row.set_child(Some(&separator));
|
||||
separator_row.set_selectable(false);
|
||||
separator_row.set_activatable(false);
|
||||
self_imp.reset_sidebar_list.append(&separator_row);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_popover_buttons(&self) {
|
||||
let self_imp = self.imp();
|
||||
self_imp
|
||||
.reset_about_button
|
||||
.connect_clicked(clone!(@ weak self as window => move |_| {
|
||||
let dialog = adw::AboutWindow::builder()
|
||||
.application_name("ReSet")
|
||||
.application_icon("ReSet")
|
||||
.developer_name("Xetibo")
|
||||
.license("GPL-3.0")
|
||||
.license_type(gtk::License::Gpl30)
|
||||
.website("https://github.com/Xetibo/ReSet")
|
||||
.issue_url("https://github.com/Xetibo/ReSet/issues")
|
||||
.version("0.0.1")
|
||||
.transient_for(&window)
|
||||
.modal(true)
|
||||
.copyright("© 2022-2023 Xetibo")
|
||||
.developers(vec!["DashieTM".to_string(), "Takotori".to_string()])
|
||||
.designers(vec!["DashieTM".to_string(), "Takotori".to_string()])
|
||||
.build();
|
||||
window.imp().reset_popover_menu.popdown();
|
||||
dialog.present();
|
||||
}));
|
||||
self_imp
|
||||
.reset_preference_button
|
||||
.connect_clicked(clone!(@weak self as window => move |_| {
|
||||
let preferences = adw::PreferencesWindow::builder().build();
|
||||
window.imp().reset_popover_menu.popdown();
|
||||
preferences.present();
|
||||
}));
|
||||
self_imp
|
||||
.reset_shortcuts_button
|
||||
.connect_clicked(clone!(@weak self as window => move |_| {
|
||||
let shortcuts = gtk::ShortcutsWindow::builder().build();
|
||||
window.imp().reset_popover_menu.popdown();
|
||||
shortcuts.present();
|
||||
}));
|
||||
}
|
||||
}
|
103
src/components/window/reset_window_impl.rs
Normal file
103
src/components/window/reset_window_impl.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use adw::glib::StaticTypeExt;
|
||||
use adw::subclass::prelude::AdwApplicationWindowImpl;
|
||||
use adw::{Breakpoint, OverlaySplitView};
|
||||
use glib::subclass::InitializingObject;
|
||||
use gtk::prelude::WidgetExt;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, Button, CompositeTemplate, FlowBox, ListBox, PopoverMenu, SearchEntry};
|
||||
|
||||
use crate::components::base::utils::{Listeners, Position};
|
||||
use crate::components::wifi::wifi_box::WifiBox;
|
||||
use crate::components::window::reset_window;
|
||||
use crate::components::window::sidebar_entry::SidebarEntry;
|
||||
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
#[template(resource = "/org/Xetibo/ReSet/resetMainWindow.ui")]
|
||||
pub struct ReSetWindow {
|
||||
#[template_child]
|
||||
pub reset_main: TemplateChild<FlowBox>,
|
||||
#[template_child]
|
||||
pub reset_sidebar_breakpoint: TemplateChild<Breakpoint>,
|
||||
#[template_child]
|
||||
pub reset_overlay_split_view: TemplateChild<OverlaySplitView>,
|
||||
#[template_child]
|
||||
pub reset_search_entry: TemplateChild<SearchEntry>,
|
||||
#[template_child]
|
||||
pub reset_sidebar_list: TemplateChild<ListBox>,
|
||||
#[template_child]
|
||||
pub reset_sidebar_toggle: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_popover_menu: TemplateChild<PopoverMenu>,
|
||||
#[template_child]
|
||||
pub reset_close: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_about_button: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_preference_button: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub reset_shortcuts_button: TemplateChild<Button>,
|
||||
pub sidebar_entries: RefCell<Vec<(SidebarEntry, Vec<SidebarEntry>)>>,
|
||||
pub listeners: Arc<Listeners>,
|
||||
pub position: Rc<RefCell<Position>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for ReSetWindow {}
|
||||
unsafe impl Sync for ReSetWindow {}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ReSetWindow {
|
||||
const ABSTRACT: bool = false;
|
||||
const NAME: &'static str = "resetUI";
|
||||
type Type = reset_window::ReSetWindow;
|
||||
type ParentType = adw::ApplicationWindow;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
WifiBox::ensure_type();
|
||||
klass.bind_template();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for ReSetWindow {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.setup_callback();
|
||||
obj.setup_popover_buttons();
|
||||
obj.handle_dynamic_sidebar();
|
||||
obj.setup_sidebar_entries();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for ReSetWindow {
|
||||
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
|
||||
self.parent_size_allocate(width, height, baseline);
|
||||
if width > 658 {
|
||||
self.reset_main.set_margin_start(60);
|
||||
self.reset_main.set_margin_end(60);
|
||||
} else {
|
||||
let div = (width - 540) / 2;
|
||||
if div > 1 {
|
||||
self.reset_main.set_margin_start(div);
|
||||
self.reset_main.set_margin_end(div);
|
||||
} else {
|
||||
self.reset_main.set_margin_start(0);
|
||||
self.reset_main.set_margin_end(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowImpl for ReSetWindow {}
|
||||
|
||||
impl ApplicationWindowImpl for ReSetWindow {}
|
||||
|
||||
impl AdwApplicationWindowImpl for ReSetWindow {}
|
53
src/components/window/sidebar_entry.rs
Normal file
53
src/components/window/sidebar_entry.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::components::base::utils::{Listeners, Position};
|
||||
use crate::components::window::sidebar_entry_impl;
|
||||
use crate::components::window::sidebar_entry_impl::{Categories, SidebarAction};
|
||||
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||
use glib::Object;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{glib, FlowBox};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct SidebarEntry(ObjectSubclass<sidebar_entry_impl::SidebarEntry>)
|
||||
@extends gtk::ListBoxRow, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
impl SidebarEntry {
|
||||
pub fn new(
|
||||
entry_name: &str,
|
||||
icon_name: &str,
|
||||
category: Categories,
|
||||
is_subcategory: bool,
|
||||
click_event: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>),
|
||||
) -> Self {
|
||||
let entry: SidebarEntry = Object::builder().build();
|
||||
let entry_imp = entry.imp();
|
||||
entry_imp.reset_sidebar_label.get().set_text(entry_name);
|
||||
entry_imp
|
||||
.reset_sidebar_image
|
||||
.set_from_icon_name(Some(icon_name));
|
||||
entry_imp.category.set(category);
|
||||
entry_imp.is_subcategory.set(is_subcategory);
|
||||
{
|
||||
let mut name = entry_imp.name.borrow_mut();
|
||||
*name = String::from(entry_name);
|
||||
let mut action = entry_imp.on_click_event.borrow_mut();
|
||||
*action = SidebarAction {
|
||||
on_click_event: click_event,
|
||||
};
|
||||
}
|
||||
Self::set_margin(&entry);
|
||||
entry
|
||||
}
|
||||
|
||||
fn set_margin(entry: &SidebarEntry) {
|
||||
if entry.imp().is_subcategory.get() {
|
||||
let option = entry.child().unwrap();
|
||||
option.set_margin_start(30);
|
||||
}
|
||||
}
|
||||
}
|
67
src/components/window/sidebar_entry_impl.rs
Normal file
67
src/components/window/sidebar_entry_impl.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use glib::subclass::InitializingObject;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, CompositeTemplate, FlowBox, Image, Label, ListBoxRow};
|
||||
|
||||
use crate::components::base::utils::{Listeners, Position};
|
||||
use crate::components::window::handle_sidebar_click::HANDLE_HOME;
|
||||
use crate::components::window::sidebar_entry;
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum Categories {
|
||||
Connectivity,
|
||||
Audio,
|
||||
Peripherals,
|
||||
#[default]
|
||||
Misc,
|
||||
}
|
||||
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
#[template(resource = "/org/Xetibo/ReSet/resetSidebarEntry.ui")]
|
||||
pub struct SidebarEntry {
|
||||
#[template_child]
|
||||
pub reset_sidebar_label: TemplateChild<Label>,
|
||||
#[template_child]
|
||||
pub reset_sidebar_image: TemplateChild<Image>,
|
||||
pub category: Cell<Categories>,
|
||||
pub is_subcategory: Cell<bool>,
|
||||
pub on_click_event: RefCell<SidebarAction>,
|
||||
pub name: RefCell<String>,
|
||||
}
|
||||
|
||||
pub struct SidebarAction {
|
||||
pub on_click_event: fn(Arc<Listeners>, FlowBox, Rc<RefCell<Position>>),
|
||||
}
|
||||
|
||||
impl Default for SidebarAction {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
on_click_event: HANDLE_HOME,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for SidebarEntry {
|
||||
const ABSTRACT: bool = false;
|
||||
const NAME: &'static str = "resetSidebarEntry";
|
||||
type Type = sidebar_entry::SidebarEntry;
|
||||
type ParentType = ListBoxRow;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for SidebarEntry {}
|
||||
|
||||
impl ListBoxRowImpl for SidebarEntry {}
|
||||
|
||||
impl WidgetImpl for SidebarEntry {}
|
84
src/main.rs
84
src/main.rs
|
@ -1,29 +1,85 @@
|
|||
mod window;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use components::window::reset_window::ReSetWindow;
|
||||
use dbus::blocking::Connection;
|
||||
use dbus::Error;
|
||||
use gtk::gdk::Display;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Application, gio};
|
||||
use window::Window;
|
||||
use gtk::{gio, Application, CssProvider};
|
||||
use reset_daemon::run_daemon;
|
||||
|
||||
mod components;
|
||||
|
||||
const APP_ID: &str = "org.Xetibo.ReSet";
|
||||
|
||||
fn main() {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tokio::task::spawn(daemon_check());
|
||||
gio::resources_register_include!("src.templates.gresource")
|
||||
.expect("Failed to register resources.");
|
||||
gio::resources_register_include!("src.icons.gresource").expect("Failed to register resources.");
|
||||
gio::resources_register_include!("src.style.gresource").expect("Failed to register resources.");
|
||||
|
||||
// Create a new application
|
||||
let app = Application::builder()
|
||||
.application_id(APP_ID)
|
||||
.build();
|
||||
let app = Application::builder().application_id(APP_ID).build();
|
||||
|
||||
app.connect_startup(move |_| {
|
||||
adw::init().unwrap();
|
||||
load_css();
|
||||
});
|
||||
|
||||
// Connect to "activate" signal of `app`
|
||||
app.connect_activate(build_ui);
|
||||
|
||||
// Run the application
|
||||
app.connect_shutdown(shutdown);
|
||||
app.run();
|
||||
}
|
||||
|
||||
fn load_css() {
|
||||
let provider = CssProvider::new();
|
||||
provider.load_from_resource("/org/Xetibo/ReSet/style/style.css");
|
||||
|
||||
gtk::style_context_add_provider_for_display(
|
||||
&Display::default().expect("Could not connect to a display."),
|
||||
&provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
}
|
||||
|
||||
fn build_ui(app: &Application) {
|
||||
// Create new window and present it
|
||||
let window = Window::new(app);
|
||||
let window = ReSetWindow::new(app);
|
||||
window.present();
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown(_: &Application) {
|
||||
thread::spawn(|| {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(100),
|
||||
);
|
||||
let res: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetDaemon", "UnregisterClient", ("ReSet",));
|
||||
res
|
||||
});
|
||||
}
|
||||
|
||||
async fn daemon_check() {
|
||||
let handle = thread::spawn(|| {
|
||||
let conn = Connection::new_session().unwrap();
|
||||
let proxy = conn.with_proxy(
|
||||
"org.Xetibo.ReSetDaemon",
|
||||
"/org/Xetibo/ReSetDaemon",
|
||||
Duration::from_millis(100),
|
||||
);
|
||||
let res: Result<(), Error> =
|
||||
proxy.method_call("org.Xetibo.ReSetDaemon", "RegisterClient", ("ReSet",));
|
||||
res
|
||||
});
|
||||
let res = handle.join();
|
||||
if res.unwrap().is_err() {
|
||||
println!("Daemon was not running");
|
||||
run_daemon().await;
|
||||
} else {
|
||||
println!("Daemon was running");
|
||||
}
|
||||
}
|
||||
|
|
73
src/resources/icons/ReSet.svg
Normal file
73
src/resources/icons/ReSet.svg
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#502100"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-settings"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="ReSet1.svg"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="38.541667"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="11.987027"
|
||||
inkscape:window-width="3440"
|
||||
inkscape:window-height="1440"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<ellipse
|
||||
cx="-12"
|
||||
cy="-12.000001"
|
||||
id="circle1"
|
||||
transform="scale(-1)"
|
||||
rx="2.25"
|
||||
ry="2.2500007"
|
||||
style="stroke-width:1.3;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 16.933331,14.000005 a 1.1000004,1.1000012 0 0 0 0.220001,1.213335 l 0.04,0.04 a 1.3333338,1.3333349 0 0 1 0,1.886671 1.3333338,1.3333349 0 0 1 -1.886668,0 l -0.04,-0.04 a 1.1000004,1.1000012 0 0 0 -1.213335,-0.219999 1.1000004,1.1000012 0 0 0 -0.666667,1.006667 v 0.113333 a 1.3333338,1.3333349 0 0 1 -1.333332,1.333326 1.3333338,1.3333349 0 0 1 -1.333334,-1.333336 v -0.06 a 1.1000004,1.1000012 0 0 0 -0.7200005,-1.006666 1.1000004,1.1000012 0 0 0 -1.213334,0.22 l -0.04,0.04 a 1.3333338,1.3333349 0 0 1 -1.8866673,0 1.3333338,1.3333349 0 0 1 0,-1.88667 l 0.04,-0.04001 A 1.1000004,1.1000012 0 0 0 7.1199943,14.053327 1.1000004,1.1000012 0 0 0 6.1133272,13.38666 H 5.9999939 A 1.3333338,1.3333349 0 0 1 4.66666,12.053336 1.3333338,1.3333349 0 0 1 5.9999939,10.720002 h 0.06 a 1.1000004,1.1000012 0 0 0 1.0066674,-0.7200024 1.1000004,1.1000012 0 0 0 -0.22,-1.2133349 l -0.04,-0.04 a 1.3333338,1.3333349 0 0 1 0,-1.8866689 1.3333338,1.3333349 0 0 1 1.8866674,0 l 0.04,0.040001 a 1.1000004,1.1000012 0 0 0 1.2133328,0.2199993 h 0.05333 A 1.1000004,1.1000012 0 0 0 10.666663,6.1133283 V 5.9999948 A 1.3333338,1.3333349 0 0 1 11.999996,4.66666 1.3333338,1.3333349 0 0 1 13.33333,5.9999948 v 0.06 a 1.1000004,1.1000012 0 0 0 0.666668,1.0066678 1.1000004,1.1000012 0 0 0 1.213333,-0.2200002 l 0.04,-0.04 a 1.3333338,1.3333349 0 0 1 1.886668,0 1.3333338,1.3333349 0 0 1 0,1.8866688 l -0.04,0.04 a 1.1000004,1.1000012 0 0 0 -0.220003,1.2133354 v 0.05333 a 1.1000004,1.1000012 0 0 0 1.006669,0.6666684 h 0.113334 A 1.3333338,1.3333349 0 0 1 19.333333,12 1.3333338,1.3333349 0 0 1 17.999999,13.333335 h -0.06 a 1.1000004,1.1000012 0 0 0 -1.006673,0.666667 z"
|
||||
id="path1"
|
||||
style="stroke-width:1.3;stroke-dasharray:none" />
|
||||
<ellipse
|
||||
style="fill:none;fill-opacity:1;stroke-width:1.3;stroke-dasharray:none"
|
||||
id="path2"
|
||||
cx="12.113708"
|
||||
cy="12.113707"
|
||||
rx="10.863708"
|
||||
ry="10.863707" />
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke-width:1.23083;stroke-dasharray:none"
|
||||
id="rect2"
|
||||
width="4.2691722"
|
||||
height="0.06917306"
|
||||
x="4.0028996"
|
||||
y="4.8591166" />
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke-width:1.23083;stroke-dasharray:none"
|
||||
id="rect2-5"
|
||||
width="0.069173172"
|
||||
height="4.2691717"
|
||||
x="3.849386"
|
||||
y="0.64830494" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
6
src/resources/icons/resources.gresource.xml
Normal file
6
src/resources/icons/resources.gresource.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/Xetibo/ReSet/icons/scalable/actions">
|
||||
<file preprocess="xml-stripblanks">ReSet.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
207
src/resources/resetAudioInput.ui
Normal file
207
src/resources/resetAudioInput.ui
Normal file
|
@ -0,0 +1,207 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<template class="resetAudioInput" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="css-classes">resetSettingLabel</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Input</property>
|
||||
<property name="margin-start">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationView">
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">output</property>
|
||||
<property name="title">output</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_source_row">
|
||||
<property name="title">Input Devices</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">go-previous-symbolic-rtl</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_cards_row">
|
||||
<property name="title">Profile Settings</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">go-previous-symbolic-rtl</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_source_dropdown">
|
||||
<property name="title">Default Input Device</property>
|
||||
<property name="use-subtitle">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_source_mute">
|
||||
<property name="icon-name">audio-input-microphone-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="reset_volume_slider">
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment">
|
||||
<property name="page-increment">2005.4016</property>
|
||||
<property name="step-increment">2005.4016</property>
|
||||
<property name="upper">100270.08</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="value-pos">bottom</property>
|
||||
<marks>
|
||||
<mark position="bottom" value="65536.0">100%</mark>
|
||||
</marks>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_volume_percentage">
|
||||
<property name="label">100%</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="width-request">40</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="reset_volume_meter">
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="reset_output_streams">
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Input Streams</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">sources</property>
|
||||
<property name="title">sources</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_output_stream_button">
|
||||
<property name="title">Input Streams</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="reset_sources">
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Input Devices</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">profileConfiguration</property>
|
||||
<property name="title">profileConfiguration</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_input_cards_back_button">
|
||||
<property name="title">Input Streams</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_cards">
|
||||
<property name="title">Devices</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
210
src/resources/resetAudioOutput.ui
Normal file
210
src/resources/resetAudioOutput.ui
Normal file
|
@ -0,0 +1,210 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<template class="resetAudioOutput" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="css-classes">resetSettingLabel</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Output</property>
|
||||
<property name="margin-start">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationView">
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">output</property>
|
||||
<property name="title">output</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_sinks_row">
|
||||
<property name="title">Output Devices</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">go-previous-symbolic-rtl</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_cards_row">
|
||||
<property name="title">Profile Settings</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">go-previous-symbolic-rtl</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_sink_dropdown">
|
||||
<property name="title">Default Output Device</property>
|
||||
<property name="use-subtitle">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_sink_mute">
|
||||
<property name="icon-name">audio-volume-high-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="reset_volume_slider">
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment">
|
||||
<property name="page-increment">2005.4016</property>
|
||||
<property name="step-increment">2005.4016</property>
|
||||
<property name="upper">100270.08</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="value-pos">bottom</property>
|
||||
<marks>
|
||||
<mark position="bottom" value="65536.0">100%</mark>
|
||||
</marks>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_volume_percentage">
|
||||
<property name="label">100%</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="width-request">40</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="reset_volume_meter">
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="reset_input_streams">
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Output Streams</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">outputDevices</property>
|
||||
<property name="title">outputDevices</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_input_stream_button">
|
||||
<property name="title">Output Streams</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="reset_sinks">
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Output Devices</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">profileConfiguration</property>
|
||||
<property name="title">profileConfiguration</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_input_cards_back_button">
|
||||
<property name="title">Output Streams</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_cards">
|
||||
<property name="title">Devices</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
62
src/resources/resetAudioSourceEntry.ui
Normal file
62
src/resources/resetAudioSourceEntry.ui
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<template class="resetAudioSourceEntry" parent="GtkBox">
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="resetSourceIcon">
|
||||
<property name="icon-name">audio-volume-high-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetSourceName">
|
||||
<property name="label">Master Volume</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="resetSourceMute">
|
||||
<property name="icon-name">audio-volume-high-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="resetVolumeSlider">
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment">
|
||||
<property name="page-increment">5.0</property>
|
||||
<property name="upper">100.0</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="value-pos">bottom</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetVolumePercentage">
|
||||
<property name="label">100%</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="width-request">40</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="resetVolumeMeter"/>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
177
src/resources/resetBluetooth.ui
Normal file
177
src/resources/resetBluetooth.ui
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<template class="resetBluetooth" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="height-request">40</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="css-classes">resetSettingLabel</property>
|
||||
<property name="label">Bluetooth</property>
|
||||
<property name="margin-start">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="reset_bluetooth_switch">
|
||||
<property name="active">True</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationView">
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">main</property>
|
||||
<property name="title">main</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_bluetooth_details">
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_bluetooth_adapter">
|
||||
<property name="title">Bluetooth Adapter</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_visibility">
|
||||
<property name="title">Visibility Settings</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">go-previous-symbolic-rtl</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_bluetooth_available_devices">
|
||||
<property name="header-suffix">
|
||||
<object class="GtkButton" id="reset_bluetooth_refresh_button">
|
||||
<property name="icon-name">view-refresh-symbolic</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="title">Available Devices</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_bluetooth_connected_devices">
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="title">Connected Devices</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">visibility</property>
|
||||
<property name="title">visibility</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkListBox">
|
||||
<property name="css-classes">boxed-list</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="show-separators">True</property>
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="resetListBoxRow" id="reset_bluetooth_main_tab">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="height-request">40</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Main</property>
|
||||
<property name="margin-start">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Visibility Settings</property>
|
||||
<property name="margin-start">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="hexpand-set">True</property>
|
||||
<property name="label">Pairable</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="reset_bluetooth_pairable_switch"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="hexpand-set">True</property>
|
||||
<property name="label">Discoverable</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="reset_bluetooth_discoverable_switch"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
9
src/resources/resetBluetoothEntry.ui
Normal file
9
src/resources/resetBluetoothEntry.ui
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.0"/>
|
||||
<template class="resetBluetoothEntry" parent="AdwActionRow">
|
||||
<property name="margin-start">5</property>
|
||||
</template>
|
||||
</interface>
|
8
src/resources/resetCardEntry.ui
Normal file
8
src/resources/resetCardEntry.ui
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<object class="AdwComboRow" id="reset_card_entry">
|
||||
<property name="use-subtitle">True</property>
|
||||
</object>
|
||||
</interface>
|
20
src/resources/resetCards.ui
Normal file
20
src/resources/resetCards.ui
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="resetCards" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="resetCardList">
|
||||
<property name="css-classes">boxed-list</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="show-separators">True</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
74
src/resources/resetInputStreamEntry.ui
Normal file
74
src/resources/resetInputStreamEntry.ui
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<template class="resetInputStreamEntry" parent="AdwPreferencesGroup">
|
||||
<property name="margin-bottom">10</property>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="margin-top">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_sink_selection">
|
||||
<property name="css-classes">audioRow</property>
|
||||
<property name="title">asadf</property>
|
||||
<property name="use-subtitle">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_sink_mute">
|
||||
<property name="icon-name">audio-input-microphone-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="reset_volume_slider">
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment">
|
||||
<property name="page-increment">2005.4016</property>
|
||||
<property name="step-increment">2005.4016</property>
|
||||
<property name="upper">100270.08</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="value-pos">bottom</property>
|
||||
<marks>
|
||||
<mark position="bottom" value="65536.0">100%</mark>
|
||||
</marks>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_volume_percentage">
|
||||
<property name="label">100%</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="width-request">40</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="reset_volume_meter">
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="css-classes">audioRow
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
9
src/resources/resetListBoxRow.ui
Normal file
9
src/resources/resetListBoxRow.ui
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="resetListBoxRow" parent="GtkListBoxRow">
|
||||
<property name="height-request">40</property>
|
||||
<property name="selectable">False</property>
|
||||
</template>
|
||||
</interface>
|
|
@ -4,398 +4,152 @@
|
|||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<template class="resetUI" parent="AdwApplicationWindow">
|
||||
<property name="default-height">500</property>
|
||||
<property name="default-width">951</property>
|
||||
<property name="height-request">200</property>
|
||||
<property name="width-request">540</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="centering-policy">strict</property>
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle">
|
||||
<property name="title">ReSet</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationSplitView">
|
||||
<property name="content">
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="child">
|
||||
<object class="GtkFlowBox" id="resetMainWindow">
|
||||
<object class="GtkWindowHandle"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwOverlaySplitView" id="reset_overlay_split_view">
|
||||
<property name="content">
|
||||
<object class="GtkBox">
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkWindowHandle">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="resetWifi_2">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwClampScrollable">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_sidebar_toggle">
|
||||
<property name="icon-name">sidebar-show-symbolic</property>
|
||||
<property name="visible">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="can-shrink">True</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="hexpand-set">True</property>
|
||||
<property name="icon-name">open-menu-symbolic</property>
|
||||
<property name="popover">
|
||||
<object class="GtkPopoverMenu" id="reset_popover_menu">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="justify">right</property>
|
||||
<property name="label">Available networks</property>
|
||||
<object class="GtkButton" id="reset_shortcuts_button">
|
||||
<property name="has-frame">False</property>
|
||||
<property name="label">Shortcuts</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBox" id="resetWifiList">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkImage" id="resetWifiStrength">
|
||||
<property name="icon-name">network-wireless-symbolic</property>
|
||||
<property name="margin-end">15</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetWifiLabel">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="resetWifiButton">
|
||||
<property name="halign">start</property>
|
||||
<property name="icon-name">emblem-system-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkImage" id="resetWifiStrength_2">
|
||||
<property name="icon-name">network-wireless-symbolic</property>
|
||||
<property name="margin-end">15</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetWifiLabel_2">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="resetWifiButton_2">
|
||||
<property name="halign">start</property>
|
||||
<property name="icon-name">emblem-system-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkImage" id="resetWifiStrength_3">
|
||||
<property name="icon-name">network-wireless-symbolic</property>
|
||||
<property name="margin-end">15</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetWifiLabel_3">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="resetWifiButton_3">
|
||||
<property name="halign">start</property>
|
||||
<property name="icon-name">emblem-system-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkImage" id="resetWifiStrength_4">
|
||||
<property name="icon-name">network-wireless-symbolic</property>
|
||||
<property name="margin-end">15</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetWifiLabel_4">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="resetWifiButton_4">
|
||||
<property name="halign">start</property>
|
||||
<property name="icon-name">emblem-system-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkImage" id="resetWifiStrength_5">
|
||||
<property name="icon-name">network-wireless-symbolic</property>
|
||||
<property name="margin-end">15</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetWifiLabel_5">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="resetWifiButton_5">
|
||||
<property name="halign">start</property>
|
||||
<property name="icon-name">emblem-system-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkImage" id="resetWifiStrength_6">
|
||||
<property name="icon-name">network-wireless-symbolic</property>
|
||||
<property name="margin-end">15</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetWifiLabel_6">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="resetWifiButton_6">
|
||||
<property name="halign">start</property>
|
||||
<property name="icon-name">emblem-system-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<object class="GtkButton" id="reset_preference_button">
|
||||
<property name="has-frame">False</property>
|
||||
<property name="label">Preferences</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_about_button">
|
||||
<property name="has-frame">False</property>
|
||||
<property name="label">About</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Advanced</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="margin-bottom">20</property>
|
||||
<property name="margin-end">20</property>
|
||||
<property name="margin-start">20</property>
|
||||
<property name="margin-top">20</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="sidebar">
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="child">
|
||||
<object class="GtkViewport">
|
||||
<property name="vscroll-policy">natural</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="width-request">150</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry">
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="placeholder-text">Search</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBox">
|
||||
<property name="css-name">resetList</property>
|
||||
<property name="width-request">150</property>
|
||||
<child>
|
||||
<object class="GtkListBoxRow" id="resetConnectivity">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">network-wired-symbolic</property>
|
||||
<property name="margin-end">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Connectivity</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBoxRow" id="resetWifi">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">30</property>
|
||||
<property name="margin-top">10</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">network-wireless-symbolic</property>
|
||||
<property name="margin-end">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">WiFi</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBoxRow" id="resetBluetooth">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">30</property>
|
||||
<property name="margin-top">10</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">bluetooth-symbolic</property>
|
||||
<property name="margin-end">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Bluetooth</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBoxRow" id="resetVPN">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">30</property>
|
||||
<property name="margin-top">10</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">network-vpn-symbolic</property>
|
||||
<property name="margin-end">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">VPN</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBoxRow" id="resetAudio">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">audio-headset-symbolic</property>
|
||||
<property name="margin-end">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Audio</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBoxRow" id="resetVolume">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">35</property>
|
||||
<property name="margin-top">10</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">audio-volume-high-symbolic</property>
|
||||
<property name="margin-end">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Volume</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBoxRow" id="resetMicrophone">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">30</property>
|
||||
<property name="margin-top">10</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">audio-input-microphone-symbolic</property>
|
||||
<property name="margin-end">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Microphone</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="b"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<object class="GtkButton" id="reset_close">
|
||||
<property name="css-classes">resetClose</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="icon-name">window-close-symbolic</property>
|
||||
<property name="margin-start">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<child>
|
||||
<object class="GtkFlowBox" id="reset_main">
|
||||
<property name="column-spacing">25</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="hexpand-set">True</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="row-spacing">25</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</property>
|
||||
<property name="max-sidebar-width">180.0</property>
|
||||
<property name="sidebar">
|
||||
<object class="GtkBox">
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkWindowHandle">
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="reset_search_entry">
|
||||
<property name="placeholder-text">Search</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="margin-top">5</property>
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<child>
|
||||
<object class="GtkListBox" id="reset_sidebar_list">
|
||||
<property name="css-name">resetList</property>
|
||||
<property name="width-request">150</property>
|
||||
<style>
|
||||
<class name="b"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<!-- Custom fragments -->
|
||||
</object>
|
||||
</child>
|
||||
<!-- Custom fragments -->
|
||||
<child>
|
||||
<object id="reset_sidebar_breakpoint" class="AdwBreakpoint">
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
73
src/resources/resetOutputStreamEntry.ui
Normal file
73
src/resources/resetOutputStreamEntry.ui
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<template class="resetOutputStreamEntry" parent="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="margin-top">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_source_selection">
|
||||
<property name="css-classes">audioRow</property>
|
||||
<property name="title">asadf</property>
|
||||
<property name="use-subtitle">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_source_mute">
|
||||
<property name="icon-name">audio-input-microphone-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="reset_volume_slider">
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment">
|
||||
<property name="page-increment">2005.4016</property>
|
||||
<property name="step-increment">2005.4016</property>
|
||||
<property name="upper">100270.08</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="value-pos">bottom</property>
|
||||
<marks>
|
||||
<mark position="bottom" value="65536.0">100%</mark>
|
||||
</marks>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_volume_percentage">
|
||||
<property name="label">100%</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="width-request">40</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="reset_volume_meter">
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="css-classes">audioRow
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
42
src/resources/resetPopup.ui
Normal file
42
src/resources/resetPopup.ui
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<template class="resetPopup" parent="GtkPopover">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_popup_label">
|
||||
<property name="visible">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkPasswordEntry" id="reset_popup_entry">
|
||||
<property name="activates-default">True</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="placeholder-text">Wifi Password</property>
|
||||
<property name="show-peek-icon">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_popup_button">
|
||||
<property name="label">connect</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
40
src/resources/resetSavedWifiEntry.ui
Normal file
40
src/resources/resetSavedWifiEntry.ui
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.0"/>
|
||||
<template class="resetSavedWifiEntry" parent="AdwActionRow">
|
||||
<property name="activatable">False</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_saved_wifi_label">
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="single-line-mode">True</property>
|
||||
<property name="width-request">200</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_edit_saved_wifi_button">
|
||||
<property name="halign">end</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">document-edit-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_delete_saved_wifi_button">
|
||||
<property name="halign">start</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="icon-name">user-trash-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
10
src/resources/resetSettingBox.ui
Normal file
10
src/resources/resetSettingBox.ui
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="resetSettingBox" parent="GtkBox">
|
||||
<property name="css-classes">settings-box</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="width-request">500</property>
|
||||
</template>
|
||||
</interface>
|
25
src/resources/resetSidebarEntry.ui
Normal file
25
src/resources/resetSidebarEntry.ui
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="resetSidebarEntry" parent="GtkListBoxRow">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="reset_sidebar_image">
|
||||
<property name="margin-end">10</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_sidebar_label"/>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="css-classes">resetSidebarEntry</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="margin-top">2</property>
|
||||
</template>
|
||||
</interface>
|
81
src/resources/resetSinkEntry.ui
Normal file
81
src/resources/resetSinkEntry.ui
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.3"/>
|
||||
<template class="resetSinkEntry" parent="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_sink_name">
|
||||
<property name="css-classes">audioRow</property>
|
||||
<property name="title">aaaaaaaaaaaaaaa</property>
|
||||
<property name="title-lines">3</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="reset_selected_sink">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="hexpand-set">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_sink_mute">
|
||||
<property name="icon-name">audio-input-microphone-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="reset_volume_slider">
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment">
|
||||
<property name="page-increment">2005.4016</property>
|
||||
<property name="step-increment">2005.4016</property>
|
||||
<property name="upper">100270.08</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="value-pos">bottom</property>
|
||||
<marks>
|
||||
<mark position="bottom" value="65536.0">100%</mark>
|
||||
</marks>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_volume_percentage">
|
||||
<property name="label">100%</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="width-request">40</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="reset_volume_meter">
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="css-classes">audioRow
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
81
src/resources/resetSourceEntry.ui
Normal file
81
src/resources/resetSourceEntry.ui
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.3"/>
|
||||
<template class="resetSourceEntry" parent="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_source_name">
|
||||
<property name="css-classes">audioRow</property>
|
||||
<property name="title">text</property>
|
||||
<property name="title-lines">3</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="reset_selected_source">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="hexpand-set">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_source_mute">
|
||||
<property name="icon-name">audio-input-microphone-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="reset_volume_slider">
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment">
|
||||
<property name="page-increment">2005.4016</property>
|
||||
<property name="step-increment">2005.4016</property>
|
||||
<property name="upper">100270.08</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="value-pos">bottom</property>
|
||||
<marks>
|
||||
<mark position="bottom" value="65536.0">100%</mark>
|
||||
</marks>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_volume_percentage">
|
||||
<property name="label">100%</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="width-request">40</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="reset_volume_meter">
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="css-classes">audioRow
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
File diff suppressed because it is too large
Load diff
|
@ -1,61 +1,109 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<object class="GtkListBoxRow" id="resetWifiEntry">
|
||||
<requires lib="gtk" version="4.6"/>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<template class="resetWifi" parent="GtkBox">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="hexpand-set">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="height-request">40</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="resetWifiStrength">
|
||||
<property name="icon-name">network-wireless-symbolic</property>
|
||||
<property name="margin-end">15</property>
|
||||
<object class="GtkLabel">
|
||||
<property name="css-classes">resetSettingLabel</property>
|
||||
<property name="label">WiFi</property>
|
||||
<property name="margin-start">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="resetWifiLabel">
|
||||
<object class="GtkSwitch" id="reset_wifi_switch">
|
||||
<property name="active">True</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="resetWifiButton">
|
||||
<property name="halign">start</property>
|
||||
<property name="icon-name">emblem-system-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkBox" id="resetWifi">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwClampScrollable">
|
||||
<property name="orientation">vertical</property>
|
||||
<object class="AdwNavigationView" id="reset_wifi_navigation">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">main</property>
|
||||
<property name="title">main</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="justify">right</property>
|
||||
<property name="label">Available networks</property>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_wifi_details">
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_wifi_device">
|
||||
<property name="title">WiFi Device</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_saved_networks">
|
||||
<property name="title">Saved Networks</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">go-previous-symbolic-rtl</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_wifi_list">
|
||||
<property name="margin-top">10</property>
|
||||
<property name="title">Available networks</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="tag">saved</property>
|
||||
<property name="title">saved</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="resetWifiList"/>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_available_networks">
|
||||
<property name="title">Available Networks</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_stored_wifi_list">
|
||||
<property name="title">Saved Networks</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="halign">start</property>
|
||||
<property name="label">Advanced</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="AdwToast"/>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
39
src/resources/resetWifiAddressEntry.ui
Normal file
39
src/resources/resetWifiAddressEntry.ui
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.2"/>
|
||||
<template class="resetWifiAddressEntry" parent="GtkBox">
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="margin-top">2</property>
|
||||
<child>
|
||||
<object class="AdwExpanderRow" id="reset_address_row">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="title">Add new Address</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_address_address">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="title">Address</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_address_prefix">
|
||||
<property name="max-width-chars">5</property>
|
||||
<property name="title">Prefix</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_address_remove">
|
||||
<property name="height-request">55</property>
|
||||
<property name="icon-name">edit-delete-symbolic</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
64
src/resources/resetWifiEntry.ui
Normal file
64
src/resources/resetWifiEntry.ui
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.0"/>
|
||||
<template class="resetWifiEntry" parent="AdwActionRow">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="margin-start">5</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="width-request">35</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="reset_wifi_strength">
|
||||
<property name="icon-name">network-wireless-signal-excellent-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="reset_wifi_encrypted">
|
||||
<property name="halign">start</property>
|
||||
<property name="icon-name">system-lock-screen-symbolic</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<property name="pixel-size">9</property>
|
||||
<property name="valign">end</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_wifi_label">
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">LoremIpsumInternet</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="single-line-mode">True</property>
|
||||
<property name="width-request">200</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="reset_wifi_connected">
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="justify">right</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="single-line-mode">True</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_wifi_edit_button">
|
||||
<property name="halign">start</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="icon-name">document-edit-symbolic</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="resetPopup" id="reset_wifi_popup"/>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
291
src/resources/resetWifiOptions.ui
Normal file
291
src/resources/resetWifiOptions.ui
Normal file
|
@ -0,0 +1,291 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.4"/>
|
||||
<template class="resetWifiOptions" parent="AdwNavigationPage">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkNotebook">
|
||||
<property name="scrollable">True</property>
|
||||
<property name="show-border">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_wifi_name">
|
||||
<property name="css-classes">property</property>
|
||||
<property name="title">WiFi Name</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_wifi_mac">
|
||||
<property name="css-classes">property</property>
|
||||
<property name="title">MAC-Address</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_wifi_link_speed">
|
||||
<property name="css-classes">property</property>
|
||||
<property name="title">Link Speed</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_wifi_ip4_addr">
|
||||
<property name="css-classes">property</property>
|
||||
<property name="title">IPv4 Address</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_wifi_ip6_addr">
|
||||
<property name="css-classes">property</property>
|
||||
<property name="title">IPv6 Address</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_wifi_gateway">
|
||||
<property name="css-classes">property</property>
|
||||
<property name="title">Gateway</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_wifi_dns">
|
||||
<property name="css-classes">property</property>
|
||||
<property name="title">DNS</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow" id="reset_wifi_last_used">
|
||||
<property name="css-classes">property</property>
|
||||
<property name="title">Last Used</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwSwitchRow" id="reset_wifi_auto_connect">
|
||||
<property name="title">Connect automatically</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwSwitchRow" id="reset_wifi_metered">
|
||||
<property name="title">Metered Connection</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="label">General</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_ip4_method">
|
||||
<property name="model">
|
||||
<object class="GtkStringList">
|
||||
<items>
|
||||
<item>Automatic (DHCP)</item>
|
||||
<item>Manual</item>
|
||||
<item>Link-Local Only</item>
|
||||
<item>Shared to other computers</item>
|
||||
<item>Disabled</item>
|
||||
</items>
|
||||
</object>
|
||||
</property>
|
||||
<property name="title">IPv4 Method</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_ip4_dns">
|
||||
<property name="title">DNS (separate IP by comma, empty for automatic)</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_ip4_gateway">
|
||||
<property name="title">Gateway</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_ip4_address_group">
|
||||
<property name="header-suffix">
|
||||
<object class="GtkButton" id="reset_ip4_address_add_button">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="title">Addresses</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_ip4_routes_group">
|
||||
<property name="header-suffix">
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Automatic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_ip4_route_add_button">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="margin-start">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="title">Routes</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="label">IPv4</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_ip6_method">
|
||||
<property name="model">
|
||||
<object class="GtkStringList">
|
||||
<items>
|
||||
<item>Automatic</item>
|
||||
<item>Automatic (DHCP)</item>
|
||||
<item>Manual</item>
|
||||
<item>Link-Local Only</item>
|
||||
<item>Shared to other computers</item>
|
||||
<item>Disabled</item>
|
||||
</items>
|
||||
</object>
|
||||
</property>
|
||||
<property name="title">IPv6 Method</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_ip6_dns">
|
||||
<property name="title">DNS (separate IP by comma, empty for automatic)</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_ip6_gateway">
|
||||
<property name="title">Gateway</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_ip6_address_group">
|
||||
<property name="header-suffix">
|
||||
<object class="GtkButton" id="reset_ip6_address_add_button">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="title">Addresses</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="reset_ip6_routes_group">
|
||||
<property name="header-suffix">
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Automatic</property>
|
||||
<property name="margin-end">5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_ip6_route_add_button">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="margin-start">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="title">Routes</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="label">IPv6</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwComboRow" id="reset_wifi_security_dropdown">
|
||||
<property name="model">
|
||||
<object class="GtkStringList">
|
||||
<items>
|
||||
<item>None</item>
|
||||
<item>WPA & WPA2 Personal</item>
|
||||
</items>
|
||||
</object>
|
||||
</property>
|
||||
<property name="title">Security</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPasswordEntryRow" id="reset_wifi_password">
|
||||
<property name="title">Password</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Security</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="wifi_options_apply_button">
|
||||
<property name="halign">end</property>
|
||||
<property name="label">Apply</property>
|
||||
<property name="margin-top">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
53
src/resources/resetWifiRouteEntry.ui
Normal file
53
src/resources/resetWifiRouteEntry.ui
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.17.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<requires lib="libadwaita" version="1.2"/>
|
||||
<template class="resetWifiRouteEntry" parent="GtkBox">
|
||||
<child>
|
||||
<object class="AdwExpanderRow" id="reset_route_row">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="title">Add new Route</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_route_address">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="title">Address</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_route_prefix">
|
||||
<property name="max-width-chars">5</property>
|
||||
<property name="title">Prefix</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_route_gateway">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="title">Gateway</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="reset_route_metric">
|
||||
<property name="max-width-chars">5</property>
|
||||
<property name="title">Metric</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_route_remove">
|
||||
<property name="height-request">55</property>
|
||||
<property name="icon-name">edit-delete-symbolic</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
|
@ -1,7 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/xetibo/reset/">
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetMainWindow.ui</file>
|
||||
<!-- <file compressed="true" preprocess="xml-stripblanks">resetWiFi.ui</file>-->
|
||||
</gresource>
|
||||
<gresource prefix="/org/Xetibo/ReSet/">
|
||||
<!--Main window-->
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetMainWindow.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetSidebarEntry.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetSettingBox.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetListBoxRow.ui</file>
|
||||
<!--WiFi-->
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetWiFi.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetWifiEntry.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetSavedWifiEntry.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetWifiOptions.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetWifiAddressEntry.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetWifiRouteEntry.ui</file>
|
||||
<!--Bluetooth-->
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetBluetooth.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetBluetoothEntry.ui</file>
|
||||
<!--Output-->
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetAudioOutput.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetOutputStreamEntry.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetSinkEntry.ui</file>
|
||||
<!--Input-->
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetAudioInput.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetInputStreamEntry.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetSourceEntry.ui</file>
|
||||
<!--Misc-->
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetPopup.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">resetCardEntry.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
|
6
src/resources/style/resources.gresource.xml
Normal file
6
src/resources/style/resources.gresource.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/Xetibo/ReSet/style">
|
||||
<file compressed="true">style.css</file>
|
||||
</gresource>
|
||||
</gresources>
|
27
src/resources/style/style.css
Normal file
27
src/resources/style/style.css
Normal file
|
@ -0,0 +1,27 @@
|
|||
button.resetClose {
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
row.resetSidebarEntry {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
label.resetSettingLabel {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
frame.resetSettingFrame {
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
label.resetSettingLabel {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
row.audioRow {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
use adw::subclass::prelude::AdwApplicationWindowImpl;
|
||||
use glib::subclass::InitializingObject;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{glib, CompositeTemplate};
|
||||
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
#[template(resource = "/org/xetibo/reset/resetMainWindow.ui")]
|
||||
pub struct Window {
|
||||
// todo i guess
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Window {
|
||||
const NAME: &'static str = "resetUI";
|
||||
type Type = super::Window;
|
||||
type ParentType = adw::ApplicationWindow;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for Window {}
|
||||
|
||||
impl WidgetImpl for Window {}
|
||||
|
||||
impl WindowImpl for Window {}
|
||||
|
||||
impl ApplicationWindowImpl for Window {}
|
||||
|
||||
impl AdwApplicationWindowImpl for Window {}
|
||||
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
mod imp;
|
||||
|
||||
use glib::Object;
|
||||
use gtk::{gio, glib, Application};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Window(ObjectSubclass<imp::Window>)
|
||||
@extends gtk::ApplicationWindow, gtk::Window, gtk::Widget,
|
||||
@implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
|
||||
gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(app: &Application) -> Self {
|
||||
// Create new window
|
||||
Object::builder()
|
||||
.property("application", app)
|
||||
.build()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue