Skip to content

Commit a7c05c8

Browse files
Fix Flatpak config loss: honour XDG base dirs
The app hardcoded ~/.config and ~/.cache. In the Flatpak that path is an ephemeral tmpfs the sandbox wipes on exit (HOME stays /home/robert but the real persistent store is $XDG_CONFIG_HOME = ~/.var/app/<id>/config). So every setting, custom button, snippet and clipboard entry vanished on restart, and disabling a recipe never held. Add xdg_paths.py (reads XDG_CONFIG_HOME/XDG_CACHE_HOME/XDG_DATA_HOME with the usual ~/.config etc. fallbacks) and route all per-user paths through it. On a normal desktop nothing moves; in the Flatpak config now lands in the persistent per-app dir. Bump to 0.9.4.
1 parent 5b773a7 commit a7c05c8

13 files changed

Lines changed: 85 additions & 26 deletions

icon_picker.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
gi.require_version("Gtk", "3.0")
2020
from gi.repository import GLib, Gtk # noqa: E402
2121

22-
USER_ICONS_DIR = Path(os.path.expanduser("~/.config/linuxpop/icons"))
22+
from xdg_paths import CONFIG_DIR # noqa: E402
23+
24+
USER_ICONS_DIR = CONFIG_DIR / "icons"
2325
HICOLOR_APPS = Path.home() / ".local/share/icons/hicolor/scalable/apps"
2426

2527
# How many icons to render per "page". GTK FlowBox is fast enough that

main.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939
from platform_backend import get_backend
4040
from popup import PopupWindow
4141
from settings import get_settings
42+
from xdg_paths import CACHE_DIR, CONFIG_DIR
4243

43-
__version__ = "0.9.3"
44+
__version__ = "0.9.4"
4445

45-
CACHE_DIR = Path(os.path.expanduser("~/.cache/linuxpop"))
4646
LOG_FILE = CACHE_DIR / "linuxpop.log"
4747
LOCK_FILE = CACHE_DIR / "linuxpop.lock"
48-
FIRST_RUN_MARKER = Path(os.path.expanduser("~/.config/linuxpop/.first-run-done"))
48+
FIRST_RUN_MARKER = CONFIG_DIR / ".first-run-done"
4949

5050
# Held for the lifetime of the process; the kernel releases the flock when
5151
# the fd is closed (i.e. when we exit, even ungracefully). Storing it at
@@ -724,8 +724,8 @@ def main(argv: list[str] | None = None) -> int:
724724
# curated default bundle from a clean state.
725725
for marker in (
726726
FIRST_RUN_MARKER,
727-
Path(os.path.expanduser("~/.config/linuxpop/.default-plugins-seeded")),
728-
Path(os.path.expanduser("~/.config/linuxpop/.default-recipes-seeded")),
727+
CONFIG_DIR / ".default-plugins-seeded",
728+
CONFIG_DIR / ".default-recipes-seeded",
729729
):
730730
try:
731731
marker.unlink(missing_ok=True)

mcp_server.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,16 @@
4242
import sys
4343
from pathlib import Path
4444

45+
sys.path.insert(0, str(Path(__file__).resolve().parent))
46+
from xdg_paths import CACHE_DIR, CONFIG_DIR # noqa: E402
47+
4548
PROTOCOL_VERSION = "2024-11-05"
4649
SERVER_NAME = "linuxpop"
47-
SERVER_VERSION = "0.9.3"
50+
SERVER_VERSION = "0.9.4"
4851

4952
# Log to a file so the user can debug without stdout-noise corrupting
5053
# the JSON-RPC stream the MCP client is reading.
51-
_LOG_DIR = Path(os.path.expanduser("~/.cache/linuxpop"))
54+
_LOG_DIR = CACHE_DIR
5255
_LOG_DIR.mkdir(parents=True, exist_ok=True)
5356
logging.basicConfig(
5457
filename=str(_LOG_DIR / "mcp.log"),
@@ -109,8 +112,7 @@ def _read_json(path: Path):
109112
def tool_get_clipboard_history(args: dict) -> dict:
110113
limit = int((args or {}).get("limit", 25))
111114
limit = max(1, min(limit, 200))
112-
history_path = Path(os.path.expanduser(
113-
"~/.cache/linuxpop/clipboard/history.json"))
115+
history_path = CACHE_DIR / "clipboard" / "history.json"
114116
raw = _read_json(history_path) or []
115117
items = []
116118
for entry in raw[:limit]:
@@ -128,8 +130,7 @@ def tool_get_clipboard_history(args: dict) -> dict:
128130

129131

130132
def tool_list_snippets(_args: dict) -> dict:
131-
snippets_path = Path(os.path.expanduser(
132-
"~/.config/linuxpop/snippets.json"))
133+
snippets_path = CONFIG_DIR / "snippets.json"
133134
raw = _read_json(snippets_path) or []
134135
items = []
135136
for entry in raw:
@@ -149,8 +150,7 @@ def tool_expand_snippet(args: dict) -> dict:
149150
name = (args or {}).get("name", "")
150151
if not name:
151152
raise ValueError("'name' is required")
152-
snippets_path = Path(os.path.expanduser(
153-
"~/.config/linuxpop/snippets.json"))
153+
snippets_path = CONFIG_DIR / "snippets.json"
154154
snippets = _read_json(snippets_path) or []
155155
match = None
156156
for s in snippets:
@@ -168,7 +168,7 @@ def tool_expand_snippet(args: dict) -> dict:
168168

169169

170170
def tool_list_recipes(_args: dict) -> dict:
171-
recipes_dir = Path(os.path.expanduser("~/.config/linuxpop/recipes"))
171+
recipes_dir = CONFIG_DIR / "recipes"
172172
items = []
173173
if recipes_dir.is_dir():
174174
for f in sorted(recipes_dir.glob("*.json")):

packaging/io.github.GaimsDevSoftware.LinuxPop.metainfo.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@
9696
<content_rating type="oars-1.1" />
9797

9898
<releases>
99+
<release version="0.9.4" date="2026-06-15">
100+
<description>
101+
<p>Your settings finally stick. In the Flatpak build the app was saving
102+
its settings, custom buttons, snippets and clipboard history to a
103+
temporary folder that the sandbox wiped on every restart, so things like
104+
switching off a search button never held. It now writes to its own
105+
permanent folder, so whatever you change stays changed.</p>
106+
</description>
107+
</release>
99108
<release version="0.9.3" date="2026-06-15">
100109
<description>
101110
<p>Flatpak: screen OCR is back (bundled tesseract + Screenshot portal);

plugin_loader.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
import actions
1818
from classifier import ContentType
1919
from plugin_base import Plugin
20+
from xdg_paths import CONFIG_DIR
2021

2122
_PLUGINS: List[Plugin] = []
2223

23-
USER_PLUGIN_DIR = Path(os.path.expanduser("~/.config/linuxpop/plugins"))
24+
USER_PLUGIN_DIR = CONFIG_DIR / "plugins"
2425
LINUXPOP_DIR = str(Path(__file__).resolve().parent)
2526
REPO_PLUGIN_DIR = Path(LINUXPOP_DIR) / "plugins_repo"
2627
ICONS_DIR = Path(LINUXPOP_DIR) / "icons"

plugin_manager.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from pathlib import Path
88
from typing import Callable
99

10+
from xdg_paths import CONFIG_DIR
11+
1012
import gi
1113

1214
gi.require_version("Gtk", "3.0")
@@ -154,8 +156,8 @@ def _force_to_front(window: Gtk.Window) -> None:
154156
pass
155157

156158
REPO_DIR = Path(__file__).resolve().parent / "plugins_repo"
157-
USER_PLUGIN_DIR = Path(os.path.expanduser("~/.config/linuxpop/plugins"))
158-
USER_RECIPE_DIR = Path(os.path.expanduser("~/.config/linuxpop/recipes"))
159+
USER_PLUGIN_DIR = CONFIG_DIR / "plugins"
160+
USER_RECIPE_DIR = CONFIG_DIR / "recipes"
159161

160162

161163
def _target_dir(entry: dict) -> Path:

plugins_repo/clipboard_history.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,11 @@ def _poll_interval() -> float:
128128
CAPTURE_IMAGES = _capture_images()
129129
POLL_INTERVAL = _poll_interval()
130130

131-
CACHE_DIR = Path(os.path.expanduser("~/.cache/linuxpop/clipboard"))
131+
from xdg_paths import CACHE_DIR as _LP_CACHE, CONFIG_DIR as _LP_CONFIG
132+
133+
CACHE_DIR = _LP_CACHE / "clipboard"
132134
HISTORY_FILE = CACHE_DIR / "history.json"
133-
SNIPPETS_FILE = Path(os.path.expanduser("~/.config/linuxpop/snippets.json"))
135+
SNIPPETS_FILE = _LP_CONFIG / "snippets.json"
134136
IMAGES_DIR = CACHE_DIR / "images"
135137

136138

plugins_repo/qr_code.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313

1414
from classifier import ContentType
1515
from plugin_base import Plugin
16+
from xdg_paths import CACHE_DIR
1617

17-
QR_CACHE = Path(os.path.expanduser("~/.cache/linuxpop/qr"))
18+
QR_CACHE = CACHE_DIR / "qr"
1819
MAX_CACHED = 20
1920

2021

recipe_loader.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030

3131
from classifier import ContentType
3232
from plugin_base import Plugin
33+
from xdg_paths import CONFIG_DIR
3334

34-
RECIPES_DIR = Path(os.path.expanduser("~/.config/linuxpop/recipes"))
35+
RECIPES_DIR = CONFIG_DIR / "recipes"
3536

3637
_CTYPE_BY_NAME = {
3738
"plain_text": ContentType.PLAIN_TEXT,

settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from pathlib import Path
1212
from typing import Any
1313

14-
CONFIG_DIR = Path(os.path.expanduser("~/.config/linuxpop"))
14+
from xdg_paths import CONFIG_DIR
15+
1516
CONFIG_PATH = CONFIG_DIR / "settings.json"
1617

1718
DEFAULTS: dict[str, Any] = {

0 commit comments

Comments
 (0)