Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,11 @@ with the Secret Key from your Emergency Kit.
Font License); it is copied to `~/.fonts` at the first start, which is
how the toolkit makes a font known. Codes and fingerprints use DejaVu
Sans Mono, which Ubuntu ships.
- **Light and dark** *(1.1)* follow the desktop where it reports one;
*Settings → Appearance* chooses one either way.
- **Light and dark** *(1.1)* follow the desktop where it reports one
(GNOME's colour scheme, or a theme whose name ends in `-dark`), asked
**once, at start**: change the desktop while SecretSpace runs and it
follows at the next start. *Settings → Appearance* chooses one either
way.
- **A package instead of an installer.** The `.deb` does what the Windows
installer does: a menu entry, data in your home folder, removal that keeps
the vaults. Unlike the Windows installer it does not offer to move a vault
Expand Down
6 changes: 6 additions & 0 deletions sourceFiles/tests/test_look.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ def window():
root.withdraw()
yield root
root.destroy()
# Collect what the window left behind here, in the thread Tk lives in.
# Fonts and images delete themselves through Tk; collected later from
# another thread (a test server's, say) each waits a second for a main
# loop that no test runs, and the request being served times out.
import gc
gc.collect()


@pytest.fixture
Expand Down
71 changes: 71 additions & 0 deletions sourceFiles/tests/test_look_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
Light and dark on Linux: the desktop is asked once.

CustomTkinter's own "system" mode asks again every 30 ms, and on Linux each
question starts a gsettings process. So this app never leaves it in that
mode: theme.apply_appearance asks once and sets light or dark.
"""
import subprocess

import pytest

pytest.importorskip("customtkinter")

import customtkinter as ctk
from customtkinter.windows.widgets.appearance_mode import AppearanceModeTracker

import theme


@pytest.fixture(autouse=True)
def put_back():
before = ctk.get_appearance_mode()
yield
ctk.set_appearance_mode(before)


def test_importing_the_theme_does_not_leave_system_mode():
assert AppearanceModeTracker.appearance_mode_set_by != "system"


@pytest.mark.parametrize("dark,shown", [(True, "Dark"), (False, "Light")])
def test_system_is_settled_once(monkeypatch, dark, shown):
asked = []
monkeypatch.setattr(theme, "desktop_is_dark",
lambda: asked.append(1) or dark)
theme.apply_appearance("system")
assert ctk.get_appearance_mode() == shown and asked == [1]
assert AppearanceModeTracker.appearance_mode_set_by != "system"


@pytest.mark.parametrize("mode", ["light", "dark"])
def test_a_chosen_mode_does_not_ask_the_desktop(monkeypatch, mode):
monkeypatch.setattr(theme, "desktop_is_dark",
lambda: pytest.fail("asked the desktop"))
theme.apply_appearance(mode)
assert ctk.get_appearance_mode() == mode.capitalize()


def _gsettings(answers):
"""A stand-in for subprocess.run that answers per key."""
def run(command, **options):
assert options.get("timeout") == 2
reply = answers[command[-1]]
if isinstance(reply, Exception):
raise reply
return subprocess.CompletedProcess(command, 0, reply.encode(), b"")
return run


@pytest.mark.parametrize("answers,dark", [
({"color-scheme": "'prefer-dark'\n"}, True),
({"color-scheme": "'default'\n"}, False),
({"color-scheme": "", "gtk-theme": "'Yaru-dark'\n"}, True), # older
({"color-scheme": "", "gtk-theme": "'Yaru'\n"}, False),
({"color-scheme": "", "gtk-theme": ""}, False), # no answer
({"color-scheme": FileNotFoundError("gsettings")}, False), # no tool
({"color-scheme": subprocess.TimeoutExpired("gsettings", 2)}, False),
])
def test_what_the_desktop_says(monkeypatch, answers, dark):
monkeypatch.setattr(subprocess, "run", _gsettings(answers))
assert theme.desktop_is_dark() is dark
36 changes: 33 additions & 3 deletions sourceFiles/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
#: What Settings offers. "system" follows the desktop.
APPEARANCES = ("system", "light", "dark")

ctk.set_appearance_mode("system")
# Not "system" here: on Linux CustomTkinter then asks the desktop again
# every 30 ms, by starting a gsettings process each time. apply_appearance
# asks once instead; the app calls it at start.
ctk.set_appearance_mode("light")
ctk.set_default_color_theme("green")

# Rounded corners are drawn the way CustomTkinter prefers here: out of
Expand Down Expand Up @@ -124,9 +127,36 @@ def pick(color):
return color


def desktop_is_dark():
"""
Whether the desktop asks for dark, from gsettings: the colour scheme,
and the theme's name on older desktops. False when it cannot say, and
it gets two seconds to answer.
"""
import subprocess
for key in ("color-scheme", "gtk-theme"):
try:
answer = subprocess.run(
["gsettings", "get", "org.gnome.desktop.interface", key],
capture_output=True, timeout=2).stdout.decode()
except Exception:
return False
if answer.strip():
return "-dark" in answer.lower()
return False


def apply_appearance(mode):
"""Switch to "system", "light" or "dark"; anything else means system."""
ctk.set_appearance_mode(mode if mode in APPEARANCES else "system")
"""
Switch to "system", "light" or "dark"; anything else means system.

"system" is settled here, once: the desktop is asked and the answer is
set as light or dark. So a desktop that changes while the app runs is
followed at the next start (or by choosing in Settings).
"""
if mode not in ("light", "dark"):
mode = "dark" if desktop_is_dark() else "light"
ctk.set_appearance_mode(mode)


def strength_color(name):
Expand Down
2 changes: 1 addition & 1 deletion sourceFiles/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import re

__version__ = "1.1.0-alpha.2"
__version__ = "1.1.0-alpha.3"

#: The container a 3.0 account's vaults are written in (account_crypto).
VAULT_FORMAT = "PMV3"
Expand Down
Loading