mirror of
https://github.com/onyx-and-iris/nvda-voicemeeter.git
synced 2025-04-19 20:13:46 +01:00
Compare commits
5 Commits
bde9020471
...
dacc972b17
Author | SHA1 | Date | |
---|---|---|---|
dacc972b17 | |||
2ed1cad666 | |||
64361b2011 | |||
36003fe73f | |||
cb82033e1c |
12
README.md
12
README.md
@ -131,9 +131,11 @@ For Gate BP Sidechain, Attack, Hold, Release you may use:
|
|||||||
|
|
||||||
To reset a slider back to its default value you may use `Control + Shift + R`.
|
To reset a slider back to its default value you may use `Control + Shift + R`.
|
||||||
|
|
||||||
#### `Menu`
|
### Menu
|
||||||
|
|
||||||
A single menu item `Voicemeeter` can be opened using `Alt` and then `v`. The menu allows you to:
|
#### `Voicemeeter`
|
||||||
|
|
||||||
|
The `Voicemeeter` menu can be opened using `Alt` and then `v`. It offers the following options:
|
||||||
|
|
||||||
- Restart Voicemeeter audio engine
|
- Restart Voicemeeter audio engine
|
||||||
- Save/Load current settings (as an xml file)
|
- Save/Load current settings (as an xml file)
|
||||||
@ -143,7 +145,11 @@ The `Save Settings` option opens a popup window with two buttons, `Browse` and `
|
|||||||
|
|
||||||
`Load Settings` and `Load on Startup` both open an Open dialog box immediately.
|
`Load Settings` and `Load on Startup` both open an Open dialog box immediately.
|
||||||
|
|
||||||
### `Quick access binds`
|
#### `Theme`
|
||||||
|
|
||||||
|
The `Theme` menu can be opened using `Alt` and then `t`. Use this menu to select from a list of coloured themes. Some themes offer higher contrast colours. An application restart is required to load a new theme. Once a theme is selected it will become the default for future startups.
|
||||||
|
|
||||||
|
### Quick access binds
|
||||||
|
|
||||||
There are a number of quick binds available to assist with faster navigation and general use.
|
There are a number of quick binds available to assist with faster navigation and general use.
|
||||||
|
|
||||||
|
37
pdm.lock
generated
37
pdm.lock
generated
@ -5,7 +5,7 @@
|
|||||||
groups = ["default", "build", "lint", "test"]
|
groups = ["default", "build", "lint", "test"]
|
||||||
strategy = ["cross_platform"]
|
strategy = ["cross_platform"]
|
||||||
lock_version = "4.4"
|
lock_version = "4.4"
|
||||||
content_hash = "sha256:ca47eaae0de5aa6bcc3fde33b6c1fa7dc2476aeb680f00bb1c550fe06ad67c55"
|
content_hash = "sha256:4295f6824f37484ec423b53bd425334b0e039a51d81261b52c8890928fcf3948"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "altgraph"
|
name = "altgraph"
|
||||||
@ -74,6 +74,31 @@ files = [
|
|||||||
{file = "macholib-1.16.2.tar.gz", hash = "sha256:557bbfa1bb255c20e9abafe7ed6cd8046b48d9525db2f9b77d3122a63a2a8bf8"},
|
{file = "macholib-1.16.2.tar.gz", hash = "sha256:557bbfa1bb255c20e9abafe7ed6cd8046b48d9525db2f9b77d3122a63a2a8bf8"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy"
|
||||||
|
version = "1.7.0"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Optional static typing for Python"
|
||||||
|
dependencies = [
|
||||||
|
"mypy-extensions>=1.0.0",
|
||||||
|
"tomli>=1.1.0; python_version < \"3.11\"",
|
||||||
|
"typing-extensions>=4.1.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"},
|
||||||
|
{file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"},
|
||||||
|
{file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"},
|
||||||
|
{file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"},
|
||||||
|
{file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"},
|
||||||
|
{file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"},
|
||||||
|
{file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"},
|
||||||
|
{file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"},
|
||||||
|
{file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"},
|
||||||
|
{file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"},
|
||||||
|
{file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"},
|
||||||
|
{file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -248,6 +273,16 @@ files = [
|
|||||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.8.0"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"},
|
||||||
|
{file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "voicemeeter-api"
|
name = "voicemeeter-api"
|
||||||
version = "2.5.2"
|
version = "2.5.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "nvda_voicemeeter"
|
name = "nvda_voicemeeter"
|
||||||
version = "0.5.6"
|
version = "0.6.0"
|
||||||
description = "A Voicemeeter app compatible with NVDA"
|
description = "A Voicemeeter app compatible with NVDA"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "onyx-and-iris", email = "code@onyxandiris.online" },
|
{ name = "onyx-and-iris", email = "code@onyxandiris.online" },
|
||||||
@ -23,6 +23,7 @@ build = [
|
|||||||
lint = [
|
lint = [
|
||||||
"black>=23.7.0",
|
"black>=23.7.0",
|
||||||
"ruff>=0.0.291",
|
"ruff>=0.0.291",
|
||||||
|
"mypy>=1.7.0",
|
||||||
]
|
]
|
||||||
test = [
|
test = [
|
||||||
"psgdemos>=1.12.1",
|
"psgdemos>=1.12.1",
|
||||||
|
@ -92,6 +92,8 @@ class Builder:
|
|||||||
return [[menu], [tab_group]]
|
return [[menu], [tab_group]]
|
||||||
|
|
||||||
def make_menu(self) -> psg.Menu:
|
def make_menu(self) -> psg.Menu:
|
||||||
|
themes = [f"{theme}::MENU THEME" for theme in util.get_themes_list()]
|
||||||
|
themes.append("Default::MENU THEME")
|
||||||
menu_def = [
|
menu_def = [
|
||||||
[
|
[
|
||||||
"&Voicemeeter",
|
"&Voicemeeter",
|
||||||
@ -102,6 +104,7 @@ class Builder:
|
|||||||
"Load Settings on Startup ::MENU",
|
"Load Settings on Startup ::MENU",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
["&Theme", themes],
|
||||||
]
|
]
|
||||||
return psg.Menu(menu_def, key="menus")
|
return psg.Menu(menu_def, key="menus")
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from .errors import NVDAVMError
|
from .errors import NVDAVMError
|
||||||
|
|
||||||
bits = 64 if ct.sizeof(ct.c_voidp) == 8 else 32
|
bits = 64 if ct.sizeof(ct.c_void_p) == 8 else 32
|
||||||
|
|
||||||
if platform.system() != "Windows":
|
if platform.system() != "Windows":
|
||||||
raise NVDAVMError("Only Windows OS supported")
|
raise NVDAVMError("Only Windows OS supported")
|
||||||
|
@ -13,7 +13,10 @@ class LabelSlider(psg.Frame):
|
|||||||
if param in ("AUDIBILITY", "DENOISER"):
|
if param in ("AUDIBILITY", "DENOISER"):
|
||||||
size = 7
|
size = 7
|
||||||
else:
|
else:
|
||||||
size = 4
|
if psg.theme() == "HighContrast":
|
||||||
|
size = 5
|
||||||
|
else:
|
||||||
|
size = 4
|
||||||
layout = [
|
layout = [
|
||||||
[
|
[
|
||||||
psg.Text(param.capitalize(), size=size),
|
psg.Text(param.capitalize(), size=size),
|
||||||
|
34
src/nvda_voicemeeter/configuration.py
Normal file
34
src/nvda_voicemeeter/configuration.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SETTINGS = Path.cwd() / "settings.json"
|
||||||
|
|
||||||
|
|
||||||
|
def config_from_json():
|
||||||
|
data = {}
|
||||||
|
if not SETTINGS.exists():
|
||||||
|
return data
|
||||||
|
with open(SETTINGS, "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
config = config_from_json()
|
||||||
|
|
||||||
|
|
||||||
|
def get(key, default=None):
|
||||||
|
if key in config:
|
||||||
|
return config[key]
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def set(key, value):
|
||||||
|
config[key] = value
|
||||||
|
with open(SETTINGS, "w") as f:
|
||||||
|
json.dump(config, f)
|
||||||
|
|
||||||
|
|
||||||
|
def delete(key):
|
||||||
|
del config[key]
|
||||||
|
with open(SETTINGS, "w") as f:
|
||||||
|
json.dump(config, f)
|
@ -1,5 +1,7 @@
|
|||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
|
import PySimpleGUI as psg
|
||||||
|
|
||||||
|
|
||||||
def get_asio_input_spinbox_index(channel, num) -> int:
|
def get_asio_input_spinbox_index(channel, num) -> int:
|
||||||
if channel == 0:
|
if channel == 0:
|
||||||
@ -196,3 +198,45 @@ def get_slider_modes() -> Iterable:
|
|||||||
|
|
||||||
def _get_bus_assignments(kind) -> list:
|
def _get_bus_assignments(kind) -> list:
|
||||||
return [f"A{i}" for i in range(1, kind.phys_out + 1)] + [f"B{i}" for i in range(1, kind.virt_out + 1)]
|
return [f"A{i}" for i in range(1, kind.phys_out + 1)] + [f"B{i}" for i in range(1, kind.virt_out + 1)]
|
||||||
|
|
||||||
|
|
||||||
|
psg.theme_add_new(
|
||||||
|
"HighContrast",
|
||||||
|
{
|
||||||
|
"BACKGROUND": "#FFFFFF",
|
||||||
|
"TEXT": "#000000",
|
||||||
|
"INPUT": "#FAF9F6",
|
||||||
|
"TEXT_INPUT": "#000000",
|
||||||
|
"SCROLL": "#FAF9F6",
|
||||||
|
"BUTTON": ("#000000", "#FFFFFF"),
|
||||||
|
"PROGRESS": ("#000000", "#FFFFFF"),
|
||||||
|
"BORDER": 2,
|
||||||
|
"SLIDER_DEPTH": 3,
|
||||||
|
"PROGRESS_DEPTH": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_themes_list() -> list:
|
||||||
|
return [
|
||||||
|
"Bright Colors",
|
||||||
|
"Dark Blue 14",
|
||||||
|
"Dark Brown 2",
|
||||||
|
"Dark Brown 3",
|
||||||
|
"Dark Green 2",
|
||||||
|
"Dark Grey 2",
|
||||||
|
"Dark Teal1",
|
||||||
|
"Dark Teal6",
|
||||||
|
"Kayak",
|
||||||
|
"Light Blue 2",
|
||||||
|
"Light Brown 2",
|
||||||
|
"Light Brown 5",
|
||||||
|
"Light Green",
|
||||||
|
"Light Green 3",
|
||||||
|
"Light Grey 2",
|
||||||
|
"Light Purple",
|
||||||
|
"Neutral Blue",
|
||||||
|
"Reds",
|
||||||
|
"Sandy Beach",
|
||||||
|
"High Contrast",
|
||||||
|
]
|
||||||
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import PySimpleGUI as psg
|
import PySimpleGUI as psg
|
||||||
|
|
||||||
from . import models, util
|
from . import configuration, models, util
|
||||||
from .builder import Builder
|
from .builder import Builder
|
||||||
from .nvda import Nvda
|
from .nvda import Nvda
|
||||||
from .parser import Parser
|
from .parser import Parser
|
||||||
@ -12,18 +12,19 @@ from .popup import Popup
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
psg.theme("Dark Blue 3")
|
psg.theme(configuration.get("default_theme", "Dark Blue 3"))
|
||||||
|
if psg.theme() == "HighContrast":
|
||||||
|
psg.set_options(font=("Arial", 14))
|
||||||
|
|
||||||
|
|
||||||
class NVDAVMWindow(psg.Window):
|
class NVDAVMWindow(psg.Window):
|
||||||
"""Represents the main window of the Voicemeeter NVDA application"""
|
"""Represents the main window of the Voicemeeter NVDA application"""
|
||||||
|
|
||||||
SETTINGS = "settings.json"
|
|
||||||
|
|
||||||
def __init__(self, title, vm):
|
def __init__(self, title, vm):
|
||||||
self.vm = vm
|
self.vm = vm
|
||||||
self.kind = self.vm.kind
|
self.kind = self.vm.kind
|
||||||
self.logger = logger.getChild(type(self).__name__)
|
self.logger = logger.getChild(type(self).__name__)
|
||||||
|
self.logger.debug(f"loaded with theme: {psg.theme()}")
|
||||||
self.cache = {
|
self.cache = {
|
||||||
"hw_ins": models._make_hardware_ins_cache(self.vm),
|
"hw_ins": models._make_hardware_ins_cache(self.vm),
|
||||||
"hw_outs": models._make_hardware_outs_cache(self.vm),
|
"hw_outs": models._make_hardware_outs_cache(self.vm),
|
||||||
@ -63,13 +64,11 @@ class NVDAVMWindow(psg.Window):
|
|||||||
self["tabgroup"].set_focus()
|
self["tabgroup"].set_focus()
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
settings_path = Path.cwd() / self.SETTINGS
|
settings_path = configuration.SETTINGS
|
||||||
if settings_path.exists():
|
if settings_path.exists():
|
||||||
try:
|
try:
|
||||||
with open(settings_path, "r") as f:
|
defaultconfig = Path(configuration.get("default_config", "")) # coerce the type
|
||||||
data = json.load(f)
|
if defaultconfig.is_file() and defaultconfig.exists():
|
||||||
defaultconfig = Path(data["default_config"])
|
|
||||||
if defaultconfig.exists():
|
|
||||||
self.vm.set("command.load", str(defaultconfig))
|
self.vm.set("command.load", str(defaultconfig))
|
||||||
self.logger.debug(f"config {defaultconfig} loaded")
|
self.logger.debug(f"config {defaultconfig} loaded")
|
||||||
self.TKroot.after(
|
self.TKroot.after(
|
||||||
@ -78,7 +77,7 @@ class NVDAVMWindow(psg.Window):
|
|||||||
f"config {defaultconfig.stem} has been loaded",
|
f"config {defaultconfig.stem} has been loaded",
|
||||||
)
|
)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
self.logger.debug("no data in settings.json. silently continuing...")
|
self.logger.debug("no default_config in settings.json. silently continuing...")
|
||||||
|
|
||||||
self.vm.init_thread()
|
self.vm.init_thread()
|
||||||
self.vm.observer.add(self.on_pdirty)
|
self.vm.observer.add(self.on_pdirty)
|
||||||
@ -510,17 +509,27 @@ class NVDAVMWindow(psg.Window):
|
|||||||
file_types=(("XML", ".xml"),),
|
file_types=(("XML", ".xml"),),
|
||||||
):
|
):
|
||||||
filepath = Path(filepath)
|
filepath = Path(filepath)
|
||||||
with open(self.SETTINGS, "w") as f:
|
configuration.set("default_settings", str(filepath))
|
||||||
json.dump({"default_config": str(filepath)}, f)
|
|
||||||
self.TKroot.after(
|
self.TKroot.after(
|
||||||
200,
|
200,
|
||||||
self.nvda.speak,
|
self.nvda.speak,
|
||||||
f"config {filepath.stem} set as default on startup",
|
f"config {filepath.stem} set as default on startup",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
with open(self.SETTINGS, "wb") as f:
|
configuration.delete("default_settings")
|
||||||
f.truncate()
|
self.logger.debug("default_settings removed from settings.json")
|
||||||
self.logger.debug("settings.json was truncated")
|
|
||||||
|
case [theme, ["MENU", "THEME"]]:
|
||||||
|
chosen = " ".join(theme)
|
||||||
|
if chosen == "Default":
|
||||||
|
chosen = "Dark Blue 3"
|
||||||
|
configuration.set("default_theme", chosen)
|
||||||
|
self.TKroot.after(
|
||||||
|
200,
|
||||||
|
self.nvda.speak,
|
||||||
|
f"theme {chosen} selected.",
|
||||||
|
)
|
||||||
|
self.logger.debug(f"theme {chosen} selected")
|
||||||
|
|
||||||
# Tabs
|
# Tabs
|
||||||
case ["tabgroup"] | [["tabgroup"], ["FOCUS", "IN"]]:
|
case ["tabgroup"] | [["tabgroup"], ["FOCUS", "IN"]]:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user