mirror of
				https://github.com/Xetibo/ReSet.git
				synced 2025-10-31 16:15:20 +01:00 
			
		
		
		
	
						commit
						6263ac59bf
					
				
					 98 changed files with 11878 additions and 650 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 takotori
						takotori