Skip to content

Commit b6bc522

Browse files
committed
Add Settings page and persist preferences locally
New Settings tab with an option to control close behaviour (minimise to tray vs quit), saved to ~/.config/nothingx-desktop/settings.json. The window honours the setting on close. https://claude.ai/code/session_014KrpftzUgVFGFXVUU3DUTT
1 parent e37d5c9 commit b6bc522

4 files changed

Lines changed: 152 additions & 5 deletions

File tree

src/nothing_ear/config.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Local persisted settings, stored as JSON under the user's config dir."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import os
7+
from pathlib import Path
8+
9+
_DEFAULTS = {
10+
"minimize_to_tray": True,
11+
}
12+
13+
14+
def _config_path() -> Path:
15+
base = os.environ.get("XDG_CONFIG_HOME") or os.path.join(Path.home(), ".config")
16+
return Path(base) / "nothingx-desktop" / "settings.json"
17+
18+
19+
class Settings:
20+
def __init__(self) -> None:
21+
self._path = _config_path()
22+
self._data = dict(_DEFAULTS)
23+
self.load()
24+
25+
@property
26+
def path(self) -> str:
27+
return str(self._path)
28+
29+
def load(self) -> None:
30+
try:
31+
with open(self._path, encoding="utf-8") as handle:
32+
stored = json.load(handle)
33+
if isinstance(stored, dict):
34+
for key in _DEFAULTS:
35+
if key in stored:
36+
self._data[key] = stored[key]
37+
except Exception:
38+
pass
39+
40+
def save(self) -> None:
41+
try:
42+
self._path.parent.mkdir(parents=True, exist_ok=True)
43+
with open(self._path, "w", encoding="utf-8") as handle:
44+
json.dump(self._data, handle, indent=2)
45+
except Exception:
46+
pass
47+
48+
def get(self, key: str):
49+
return self._data.get(key, _DEFAULTS.get(key))
50+
51+
def set(self, key: str, value) -> None:
52+
self._data[key] = value
53+
self.save()

src/nothing_ear/ui/main_window.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626

2727
from .. import __version__
2828
from ..bluetooth import BtDevice
29+
from ..config import Settings
2930
from .anc_tab import AncTab
3031
from .eq_tab import EqTab
32+
from .settings_tab import SettingsTab
3133
from .style import STYLESHEET
3234

3335
_ICON_PATH = Path(__file__).resolve().parent.parent / "assets" / "icon.svg"
@@ -55,6 +57,7 @@ def __init__(self, mock: bool = False):
5557
self.setStyleSheet(STYLESHEET)
5658

5759
self._mock = mock
60+
self._settings = Settings()
5861
self._devices: list[BtDevice] = []
5962
self._primary_path: str | None = None
6063
self._last_signature: tuple | None = None
@@ -121,10 +124,7 @@ def _show_window(self) -> None:
121124

122125
def _quit(self) -> None:
123126
self._force_quit = True
124-
self._service.stop()
125-
if self._tray is not None:
126-
self._tray.hide()
127-
QApplication.instance().quit()
127+
self.close()
128128

129129
def _build_ui(self) -> None:
130130
root = QWidget()
@@ -214,6 +214,7 @@ def _build_tabs(self) -> QWidget:
214214
tabs.addTab(self._anc_tab, "ANC")
215215
self._eq_tab = EqTab(mock=self._mock)
216216
tabs.addTab(self._eq_tab, "EQ")
217+
tabs.addTab(SettingsTab(self._settings), "Settings")
217218

218219
return tabs
219220

@@ -311,7 +312,12 @@ def _on_error(self, message: str) -> None:
311312
self._status.setText(f"⚠ {message}")
312313

313314
def closeEvent(self, event) -> None:
314-
if self._tray is not None and not self._force_quit:
315+
minimize = (
316+
self._tray is not None
317+
and bool(self._settings.get("minimize_to_tray"))
318+
and not self._force_quit
319+
)
320+
if minimize:
315321
event.ignore()
316322
self.hide()
317323
if not self._tray_notified:
@@ -324,4 +330,7 @@ def closeEvent(self, event) -> None:
324330
self._tray_notified = True
325331
return
326332
self._service.stop()
333+
if self._tray is not None:
334+
self._tray.hide()
327335
super().closeEvent(event)
336+
QApplication.instance().quit()

src/nothing_ear/ui/settings_tab.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Settings tab: app preferences persisted to a local config file."""
2+
3+
from __future__ import annotations
4+
5+
from PySide6.QtWidgets import (
6+
QCheckBox,
7+
QFrame,
8+
QLabel,
9+
QVBoxLayout,
10+
QWidget,
11+
)
12+
13+
from .. import __version__
14+
from ..config import Settings
15+
16+
17+
class SettingsTab(QWidget):
18+
def __init__(self, settings: Settings) -> None:
19+
super().__init__()
20+
self._settings = settings
21+
self._build()
22+
23+
def _build(self) -> None:
24+
root = QVBoxLayout(self)
25+
root.setContentsMargins(0, 16, 0, 0)
26+
root.setSpacing(14)
27+
28+
window_card = QFrame()
29+
window_card.setObjectName("card")
30+
wv = QVBoxLayout(window_card)
31+
wv.setContentsMargins(18, 18, 18, 18)
32+
wv.setSpacing(8)
33+
34+
wtitle = QLabel("Window")
35+
wtitle.setObjectName("sectionTitle")
36+
wv.addWidget(wtitle)
37+
38+
self._tray_checkbox = QCheckBox("Minimise to tray when closing the window")
39+
self._tray_checkbox.setChecked(bool(self._settings.get("minimize_to_tray")))
40+
self._tray_checkbox.toggled.connect(self._on_tray_toggled)
41+
wv.addWidget(self._tray_checkbox)
42+
43+
hint = QLabel("When off, closing the window quits the app instead of keeping it running in the tray.")
44+
hint.setObjectName("deviceMeta")
45+
hint.setWordWrap(True)
46+
wv.addWidget(hint)
47+
48+
root.addWidget(window_card)
49+
50+
about_card = QFrame()
51+
about_card.setObjectName("card")
52+
av = QVBoxLayout(about_card)
53+
av.setContentsMargins(18, 18, 18, 18)
54+
av.setSpacing(6)
55+
56+
atitle = QLabel("About")
57+
atitle.setObjectName("sectionTitle")
58+
av.addWidget(atitle)
59+
av.addWidget(self._meta(f"NothingX Desktop · v{__version__}"))
60+
av.addWidget(self._meta(f"Settings file: {self._settings.path}"))
61+
62+
root.addWidget(about_card)
63+
root.addStretch(1)
64+
65+
def _meta(self, text: str) -> QLabel:
66+
label = QLabel(text)
67+
label.setObjectName("deviceMeta")
68+
label.setWordWrap(True)
69+
return label
70+
71+
def _on_tray_toggled(self, checked: bool) -> None:
72+
self._settings.set("minimize_to_tray", bool(checked))

src/nothing_ear/ui/style.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,19 @@
121121
border-radius: 8px;
122122
}
123123
124+
QCheckBox { spacing: 10px; color: #f5f5f5; }
125+
QCheckBox::indicator {
126+
width: 20px;
127+
height: 20px;
128+
border: 1px solid #3a3a3a;
129+
border-radius: 6px;
130+
background: #161616;
131+
}
132+
QCheckBox::indicator:checked {
133+
background: #ffffff;
134+
border-color: #ffffff;
135+
}
136+
124137
QDialog { background-color: #0d0d0d; }
125138
126139
QScrollArea { border: none; background: transparent; }

0 commit comments

Comments
 (0)