Implemented local settings save in ~/.local/share/stormux. Also, I think I got interrupt on any keypress working better when using -x.
This commit is contained in:
+1
-1
@@ -41,7 +41,7 @@ def create_argument_parser():
|
|||||||
argumentParser.add_argument(
|
argumentParser.add_argument(
|
||||||
'-s', '--setting',
|
'-s', '--setting',
|
||||||
metavar='SETTING-FILE',
|
metavar='SETTING-FILE',
|
||||||
default='/etc/fenrir/settings/settings.conf',
|
default=None,
|
||||||
help='Path to custom settings file'
|
help='Path to custom settings file'
|
||||||
)
|
)
|
||||||
argumentParser.add_argument(
|
argumentParser.add_argument(
|
||||||
|
|||||||
@@ -22,10 +22,7 @@ class command:
|
|||||||
return _("Saves your current Fenrir settings so they are the default.")
|
return _("Saves your current Fenrir settings so they are the default.")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
settings_file = self.env["runtime"][
|
self.env["runtime"]["SettingsManager"].save_settings()
|
||||||
"SettingsManager"
|
|
||||||
].get_settings_file()
|
|
||||||
self.env["runtime"]["SettingsManager"].save_settings(settings_file)
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Settings saved."), interrupt=True
|
_("Settings saved."), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ class DynamicKeyboardLayoutCommand:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Save to the actual config file
|
# Save to the actual config file
|
||||||
configFilePath = settingsManager.get_settings_file()
|
settingsManager.save_settings()
|
||||||
settingsManager.save_settings(configFilePath)
|
|
||||||
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
f"Keyboard layout set to {self.layoutName}. Please restart Fenrir for this change to take effect."
|
f"Keyboard layout set to {self.layoutName}. Please restart Fenrir for this change to take effect."
|
||||||
|
|||||||
@@ -464,12 +464,6 @@ class RemoteManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def save_settings(self, setting_config_path=None):
|
def save_settings(self, setting_config_path=None):
|
||||||
if not setting_config_path:
|
|
||||||
setting_config_path = self.env["runtime"][
|
|
||||||
"SettingsManager"
|
|
||||||
].get_settings_file()
|
|
||||||
if setting_config_path == "":
|
|
||||||
return
|
|
||||||
self.env["runtime"]["SettingsManager"].save_settings(
|
self.env["runtime"]["SettingsManager"].save_settings(
|
||||||
setting_config_path
|
setting_config_path
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,11 +42,18 @@ fenrir_path = os.path.dirname(currentdir)
|
|||||||
|
|
||||||
|
|
||||||
class SettingsManager:
|
class SettingsManager:
|
||||||
|
system_settings_root = "/etc/fenrirscreenreader/"
|
||||||
|
system_settings_file = "/etc/fenrirscreenreader/settings/settings.conf"
|
||||||
|
user_settings_file = (
|
||||||
|
"~/.local/share/stormux/fenrirscreenreader/settings/settings.conf"
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.settings = settings_data
|
self.settings = settings_data
|
||||||
self.settingArgDict = {}
|
self.settingArgDict = {}
|
||||||
self.bindingsBackup = None
|
self.bindingsBackup = None
|
||||||
self.settings_file = ""
|
self.settings_file = ""
|
||||||
|
self.save_settings_path = ""
|
||||||
|
|
||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
self.env = environment
|
self.env = environment
|
||||||
@@ -104,12 +111,57 @@ class SettingsManager:
|
|||||||
return self.settings_file
|
return self.settings_file
|
||||||
|
|
||||||
def set_settings_file(self, settings_file):
|
def set_settings_file(self, settings_file):
|
||||||
if not os.path.exists(settings_file):
|
if os.path.exists(settings_file) and not os.access(
|
||||||
return
|
settings_file, os.R_OK
|
||||||
if not os.access(settings_file, os.R_OK):
|
):
|
||||||
return
|
return
|
||||||
self.settings_file = settings_file
|
self.settings_file = settings_file
|
||||||
|
|
||||||
|
def get_user_settings_file(self):
|
||||||
|
return os.path.expanduser(self.user_settings_file)
|
||||||
|
|
||||||
|
def get_system_settings_file(self):
|
||||||
|
return self.system_settings_file
|
||||||
|
|
||||||
|
def is_user_settings_mode(self):
|
||||||
|
return os.geteuid() != 0
|
||||||
|
|
||||||
|
def get_default_save_settings_file(self):
|
||||||
|
if self.is_user_settings_mode():
|
||||||
|
return self.get_user_settings_file()
|
||||||
|
return self.get_system_settings_file()
|
||||||
|
|
||||||
|
def get_bundled_settings_root(self):
|
||||||
|
return os.path.abspath(os.path.join(fenrir_path, "../../config/")) + "/"
|
||||||
|
|
||||||
|
def get_resource_settings_root(self):
|
||||||
|
if os.path.exists(self.system_settings_root):
|
||||||
|
return self.system_settings_root
|
||||||
|
bundled_settings_root = self.get_bundled_settings_root()
|
||||||
|
if os.path.exists(bundled_settings_root):
|
||||||
|
return bundled_settings_root
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def resolve_settings_file(self, requested_settings_file=None):
|
||||||
|
if requested_settings_file and os.path.exists(requested_settings_file):
|
||||||
|
return requested_settings_file
|
||||||
|
|
||||||
|
if self.is_user_settings_mode() and os.path.exists(
|
||||||
|
self.get_user_settings_file()
|
||||||
|
):
|
||||||
|
return self.get_user_settings_file()
|
||||||
|
|
||||||
|
if os.path.exists(self.get_system_settings_file()):
|
||||||
|
return self.get_system_settings_file()
|
||||||
|
|
||||||
|
bundled_settings_file = os.path.join(
|
||||||
|
self.get_bundled_settings_root(), "settings/settings.conf"
|
||||||
|
)
|
||||||
|
if os.path.exists(bundled_settings_file):
|
||||||
|
return bundled_settings_file
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
def load_settings(self, setting_config_path):
|
def load_settings(self, setting_config_path):
|
||||||
if not os.path.exists(setting_config_path):
|
if not os.path.exists(setting_config_path):
|
||||||
return False
|
return False
|
||||||
@@ -128,7 +180,29 @@ class SettingsManager:
|
|||||||
self.set_settings_file(setting_config_path)
|
self.set_settings_file(setting_config_path)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def save_settings(self, setting_config_path):
|
def save_settings(self, setting_config_path=None):
|
||||||
|
if setting_config_path is None:
|
||||||
|
if self.save_settings_path:
|
||||||
|
setting_config_path = self.save_settings_path
|
||||||
|
else:
|
||||||
|
setting_config_path = self.get_default_save_settings_file()
|
||||||
|
|
||||||
|
# Ensure directory exists for user-local settings
|
||||||
|
if setting_config_path and setting_config_path.startswith(
|
||||||
|
os.path.expanduser("~/.local/")
|
||||||
|
):
|
||||||
|
config_dir = os.path.dirname(setting_config_path)
|
||||||
|
if not os.path.exists(config_dir):
|
||||||
|
try:
|
||||||
|
os.makedirs(config_dir, mode=0o755, exist_ok=True)
|
||||||
|
except Exception as e:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"save_settings: failed to create directory "
|
||||||
|
+ config_dir
|
||||||
|
+ ": "
|
||||||
|
+ str(e),
|
||||||
|
debug.DebugLevel.ERROR,
|
||||||
|
)
|
||||||
# set opt dict here
|
# set opt dict here
|
||||||
# save file
|
# save file
|
||||||
try:
|
try:
|
||||||
@@ -458,25 +532,24 @@ class SettingsManager:
|
|||||||
def init_fenrir_config(
|
def init_fenrir_config(
|
||||||
self, cliArgs, fenrir_manager=None, environment=environment.environment
|
self, cliArgs, fenrir_manager=None, environment=environment.environment
|
||||||
):
|
):
|
||||||
settings_root = "/etc/fenrirscreenreader/"
|
self.cliArgs = cliArgs
|
||||||
settings_file = cliArgs.setting
|
|
||||||
|
settings_root = self.get_resource_settings_root()
|
||||||
|
if not settings_root:
|
||||||
|
return None
|
||||||
|
|
||||||
sound_root = "/usr/share/sounds/fenrirscreenreader/"
|
sound_root = "/usr/share/sounds/fenrirscreenreader/"
|
||||||
# get fenrir settings root
|
|
||||||
if not os.path.exists(settings_root):
|
|
||||||
if os.path.exists(fenrir_path + "/../../config/"):
|
|
||||||
settings_root = fenrir_path + "/../../config/"
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
# get settings file
|
|
||||||
if settings_file is None or not os.path.exists(settings_file):
|
|
||||||
if os.path.exists(settings_root + "/settings/settings.conf"):
|
|
||||||
settings_file = settings_root + "/settings/settings.conf"
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
# get sound themes root
|
|
||||||
if not os.path.exists(sound_root):
|
if not os.path.exists(sound_root):
|
||||||
if os.path.exists(fenrir_path + "/../../config/sound/"):
|
bundled_sound_root = os.path.join(
|
||||||
sound_root = fenrir_path + "/../../config/sound/"
|
self.get_bundled_settings_root(), "sound/"
|
||||||
|
)
|
||||||
|
if os.path.exists(bundled_sound_root):
|
||||||
|
sound_root = bundled_sound_root
|
||||||
|
|
||||||
|
self.save_settings_path = self.get_default_save_settings_file()
|
||||||
|
settings_file = self.resolve_settings_file(cliArgs.setting)
|
||||||
|
if not settings_file:
|
||||||
|
return None
|
||||||
|
|
||||||
environment["runtime"]["SettingsManager"] = self
|
environment["runtime"]["SettingsManager"] = self
|
||||||
environment["runtime"]["SettingsManager"].initialize(environment)
|
environment["runtime"]["SettingsManager"].initialize(environment)
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2026.05.10"
|
version = "2026.05.12"
|
||||||
code_name = "testing"
|
code_name = "testing"
|
||||||
|
|||||||
@@ -288,6 +288,20 @@ class driver(screenDriver):
|
|||||||
msg_bytes = bytes(msg_bytes, "UTF-8")
|
msg_bytes = bytes(msg_bytes, "UTF-8")
|
||||||
os.write(screen, msg_bytes)
|
os.write(screen, msg_bytes)
|
||||||
|
|
||||||
|
def interrupt_output_on_stdin_input(self, msg_bytes):
|
||||||
|
if not msg_bytes:
|
||||||
|
return
|
||||||
|
settings_manager = self.env["runtime"]["SettingsManager"]
|
||||||
|
if not settings_manager.get_setting_as_bool(
|
||||||
|
"keyboard", "interrupt_on_key_press"
|
||||||
|
):
|
||||||
|
return
|
||||||
|
if settings_manager.get_setting(
|
||||||
|
"keyboard", "interrupt_on_key_press_filter"
|
||||||
|
).strip():
|
||||||
|
return
|
||||||
|
self.env["runtime"]["OutputManager"].interrupt_output()
|
||||||
|
|
||||||
def get_session_information(self):
|
def get_session_information(self):
|
||||||
self.env["screen"]["autoIgnoreScreens"] = []
|
self.env["screen"]["autoIgnoreScreens"] = []
|
||||||
self.env["general"]["prev_user"] = getpass.getuser()
|
self.env["general"]["prev_user"] = getpass.getuser()
|
||||||
@@ -420,6 +434,7 @@ class driver(screenDriver):
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
|
self.interrupt_output_on_stdin_input(msg_bytes)
|
||||||
self.inject_text_to_screen(msg_bytes)
|
self.inject_text_to_screen(msg_bytes)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.env["runtime"][
|
self.env["runtime"][
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from fenrirscreenreader.screenDriver.ptyDriver import PTYConstants
|
from fenrirscreenreader.screenDriver.ptyDriver import PTYConstants
|
||||||
from fenrirscreenreader.screenDriver.ptyDriver import Terminal
|
from fenrirscreenreader.screenDriver.ptyDriver import Terminal
|
||||||
@@ -52,3 +53,59 @@ def test_optional_float_setting_uses_default_when_missing():
|
|||||||
)
|
)
|
||||||
== PTYConstants.OUTPUT_READ_TIMEOUT
|
== PTYConstants.OUTPUT_READ_TIMEOUT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_pty_stdin_input_interrupts_output_when_all_keys_interrupt_enabled():
|
||||||
|
pty_driver = PtyDriver()
|
||||||
|
settings_manager = Mock()
|
||||||
|
settings_manager.get_setting_as_bool.return_value = True
|
||||||
|
settings_manager.get_setting.return_value = ""
|
||||||
|
output_manager = Mock()
|
||||||
|
pty_driver.env = {
|
||||||
|
"runtime": {
|
||||||
|
"SettingsManager": settings_manager,
|
||||||
|
"OutputManager": output_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pty_driver.interrupt_output_on_stdin_input(b"a")
|
||||||
|
|
||||||
|
output_manager.interrupt_output.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_pty_stdin_input_honors_interrupt_disabled():
|
||||||
|
pty_driver = PtyDriver()
|
||||||
|
settings_manager = Mock()
|
||||||
|
settings_manager.get_setting_as_bool.return_value = False
|
||||||
|
output_manager = Mock()
|
||||||
|
pty_driver.env = {
|
||||||
|
"runtime": {
|
||||||
|
"SettingsManager": settings_manager,
|
||||||
|
"OutputManager": output_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pty_driver.interrupt_output_on_stdin_input(b"a")
|
||||||
|
|
||||||
|
output_manager.interrupt_output.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_pty_stdin_input_leaves_filtered_interrupts_to_key_events():
|
||||||
|
pty_driver = PtyDriver()
|
||||||
|
settings_manager = Mock()
|
||||||
|
settings_manager.get_setting_as_bool.return_value = True
|
||||||
|
settings_manager.get_setting.return_value = "KEY_ENTER"
|
||||||
|
output_manager = Mock()
|
||||||
|
pty_driver.env = {
|
||||||
|
"runtime": {
|
||||||
|
"SettingsManager": settings_manager,
|
||||||
|
"OutputManager": output_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pty_driver.interrupt_output_on_stdin_input(b"a")
|
||||||
|
|
||||||
|
output_manager.interrupt_output.assert_not_called()
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ for all configurable settings that could cause crashes or accessibility issues.
|
|||||||
import pytest
|
import pytest
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
# Import the settings manager
|
# Import the settings manager
|
||||||
from fenrirscreenreader.core.settingsData import settings_data
|
from fenrirscreenreader.core.settingsData import settings_data
|
||||||
from fenrirscreenreader.core.settingsManager import SettingsManager
|
from fenrirscreenreader.core.settingsManager import SettingsManager
|
||||||
|
from fenrirscreenreader.commands.commands import save_settings
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@@ -198,3 +200,93 @@ class TestValidationSkipsUnknownSettings:
|
|||||||
def test_focus_settings_define_tui_toggle():
|
def test_focus_settings_define_tui_toggle():
|
||||||
"""Focus settings should include the TUI toggle used by on-screen handlers."""
|
"""Focus settings should include the TUI toggle used by on-screen handlers."""
|
||||||
assert settings_data["focus"]["tui"] is False
|
assert settings_data["focus"]["tui"] is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.settings
|
||||||
|
class TestSettingsPathSelection:
|
||||||
|
"""Test root/user settings load and save path selection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.manager = SettingsManager()
|
||||||
|
|
||||||
|
def configure_paths(self, tmp_path):
|
||||||
|
system_root = tmp_path / "etc" / "fenrirscreenreader"
|
||||||
|
system_file = system_root / "settings" / "settings.conf"
|
||||||
|
user_file = (
|
||||||
|
tmp_path
|
||||||
|
/ "home"
|
||||||
|
/ "Username"
|
||||||
|
/ ".local"
|
||||||
|
/ "share"
|
||||||
|
/ "stormux"
|
||||||
|
/ "fenrirscreenreader"
|
||||||
|
/ "settings"
|
||||||
|
/ "settings.conf"
|
||||||
|
)
|
||||||
|
self.manager.system_settings_root = str(system_root) + "/"
|
||||||
|
self.manager.system_settings_file = str(system_file)
|
||||||
|
self.manager.user_settings_file = str(user_file)
|
||||||
|
return system_file, user_file
|
||||||
|
|
||||||
|
def test_non_root_loads_user_settings_when_present(
|
||||||
|
self, tmp_path, monkeypatch
|
||||||
|
):
|
||||||
|
system_file, user_file = self.configure_paths(tmp_path)
|
||||||
|
system_file.parent.mkdir(parents=True)
|
||||||
|
system_file.write_text("[general]\n", encoding="utf-8")
|
||||||
|
user_file.parent.mkdir(parents=True)
|
||||||
|
user_file.write_text("[general]\n", encoding="utf-8")
|
||||||
|
monkeypatch.setattr("os.geteuid", lambda: 1000)
|
||||||
|
|
||||||
|
assert self.manager.resolve_settings_file() == str(user_file)
|
||||||
|
|
||||||
|
def test_non_root_falls_back_to_system_settings(
|
||||||
|
self, tmp_path, monkeypatch
|
||||||
|
):
|
||||||
|
system_file, _user_file = self.configure_paths(tmp_path)
|
||||||
|
system_file.parent.mkdir(parents=True)
|
||||||
|
system_file.write_text("[general]\n", encoding="utf-8")
|
||||||
|
monkeypatch.setattr("os.geteuid", lambda: 1000)
|
||||||
|
|
||||||
|
assert self.manager.resolve_settings_file() == str(system_file)
|
||||||
|
|
||||||
|
def test_root_uses_system_settings_even_when_user_settings_exists(
|
||||||
|
self, tmp_path, monkeypatch
|
||||||
|
):
|
||||||
|
system_file, user_file = self.configure_paths(tmp_path)
|
||||||
|
system_file.parent.mkdir(parents=True)
|
||||||
|
system_file.write_text("[general]\n", encoding="utf-8")
|
||||||
|
user_file.parent.mkdir(parents=True)
|
||||||
|
user_file.write_text("[general]\n", encoding="utf-8")
|
||||||
|
monkeypatch.setattr("os.geteuid", lambda: 0)
|
||||||
|
|
||||||
|
assert self.manager.resolve_settings_file() == str(system_file)
|
||||||
|
|
||||||
|
def test_save_default_path_follows_effective_user(
|
||||||
|
self, tmp_path, monkeypatch
|
||||||
|
):
|
||||||
|
system_file, user_file = self.configure_paths(tmp_path)
|
||||||
|
|
||||||
|
monkeypatch.setattr("os.geteuid", lambda: 1000)
|
||||||
|
assert self.manager.get_default_save_settings_file() == str(user_file)
|
||||||
|
|
||||||
|
monkeypatch.setattr("os.geteuid", lambda: 0)
|
||||||
|
assert self.manager.get_default_save_settings_file() == str(system_file)
|
||||||
|
|
||||||
|
def test_save_settings_command_uses_default_save_path(self):
|
||||||
|
manager = Mock()
|
||||||
|
output_manager = Mock()
|
||||||
|
command = save_settings.command()
|
||||||
|
command.initialize(
|
||||||
|
{
|
||||||
|
"runtime": {
|
||||||
|
"SettingsManager": manager,
|
||||||
|
"OutputManager": output_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
command.run()
|
||||||
|
|
||||||
|
manager.save_settings.assert_called_once_with()
|
||||||
|
|||||||
Reference in New Issue
Block a user