lightdark 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lightdark/__init__.py +0 -0
- lightdark/__main__.py +3 -0
- lightdark/main.py +81 -0
- lightdark/modules.py +134 -0
- lightdark/py.typed +0 -0
- lightdark-0.9.0.dist-info/METADATA +30 -0
- lightdark-0.9.0.dist-info/RECORD +12 -0
- lightdark-0.9.0.dist-info/WHEEL +5 -0
- lightdark-0.9.0.dist-info/entry_points.txt +2 -0
- lightdark-0.9.0.dist-info/licenses/AUTHORS.md +9 -0
- lightdark-0.9.0.dist-info/licenses/LICENSE +0 -0
- lightdark-0.9.0.dist-info/top_level.txt +1 -0
lightdark/__init__.py
ADDED
|
File without changes
|
lightdark/__main__.py
ADDED
lightdark/main.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
import atomicwrites
|
|
7
|
+
import mergedeep
|
|
8
|
+
from platformdirs import PlatformDirs
|
|
9
|
+
from . import modules as mods
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
ap = argparse.ArgumentParser()
|
|
16
|
+
ap.add_argument("-t", "--theme", help="set to this theme instead of cycling")
|
|
17
|
+
ap.add_argument("-k", "--keep", help="keep the same theme instead of cycling", action="store_true")
|
|
18
|
+
ap.add_argument("-v", "--verbose", help="show errors from module configuration", action="store_true")
|
|
19
|
+
args = ap.parse_args()
|
|
20
|
+
|
|
21
|
+
pd = PlatformDirs(appname="lightdark")
|
|
22
|
+
config_dirs = [Path(d) for d in pd.iter_config_dirs()]
|
|
23
|
+
for d in config_dirs:
|
|
24
|
+
if (state_path := d / "current.txt").exists():
|
|
25
|
+
break
|
|
26
|
+
else:
|
|
27
|
+
state_path = config_dirs[0] / "current.txt"
|
|
28
|
+
|
|
29
|
+
config_dicts = []
|
|
30
|
+
config_paths = []
|
|
31
|
+
for d in config_dirs[::-1]:
|
|
32
|
+
config_paths.append(config_path := d / "config.json")
|
|
33
|
+
if config_path.exists():
|
|
34
|
+
try:
|
|
35
|
+
config_dicts.append(json.loads(config_path.read_bytes()))
|
|
36
|
+
except Exception:
|
|
37
|
+
logger.warning("failed to read", extra=dict(data_path=str(config_path)))
|
|
38
|
+
|
|
39
|
+
if not config_dicts:
|
|
40
|
+
raise RuntimeError(f"config.json not found, paths searched: {config_paths!r}")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
old_theme = state_path.read_bytes().decode("utf-8").strip()
|
|
44
|
+
except Exception:
|
|
45
|
+
old_theme = None
|
|
46
|
+
|
|
47
|
+
mergedeep.merge((config := {}), *config_dicts[::-1])
|
|
48
|
+
|
|
49
|
+
if args.keep:
|
|
50
|
+
new_theme = old_theme
|
|
51
|
+
elif args.theme:
|
|
52
|
+
new_theme = args.theme
|
|
53
|
+
else:
|
|
54
|
+
cycle = config["cycle"]
|
|
55
|
+
try:
|
|
56
|
+
i = (cycle.index(old_theme) + 1) % len(cycle)
|
|
57
|
+
except ValueError:
|
|
58
|
+
i = 0
|
|
59
|
+
new_theme = cycle[i]
|
|
60
|
+
|
|
61
|
+
with atomicwrites.atomic_write(state_path, mode="wt", encoding="utf-8", overwrite=True) as f:
|
|
62
|
+
f.write(new_theme)
|
|
63
|
+
|
|
64
|
+
theme_config = config["themes"][new_theme]
|
|
65
|
+
|
|
66
|
+
# now apply the theme:
|
|
67
|
+
utility = mods.Utility()
|
|
68
|
+
for cls in mods.Module.REGISTRY.values():
|
|
69
|
+
try:
|
|
70
|
+
module: mods.Module = cls.from_config(
|
|
71
|
+
config=theme_config[cls.config_key], theme_name=new_theme, utility=utility, global_config=config
|
|
72
|
+
)
|
|
73
|
+
except Exception:
|
|
74
|
+
# mostly likely the configuration is not set
|
|
75
|
+
if args.verbose:
|
|
76
|
+
logger.warning(f"error initializing configuration module: {cls!r}", exc_info=True)
|
|
77
|
+
continue
|
|
78
|
+
try:
|
|
79
|
+
module.run()
|
|
80
|
+
except Exception:
|
|
81
|
+
logger.warning(f"error applying configuration module: {module!r}", exc_info=True)
|
lightdark/modules.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import re
|
|
3
|
+
import subprocess as sbp
|
|
4
|
+
|
|
5
|
+
import attr
|
|
6
|
+
import atomicwrites
|
|
7
|
+
|
|
8
|
+
sbp_run_pipe_check_kw = dict(stdout=sbp.PIPE, stderr=sbp.PIPE, check=True)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Utility:
|
|
12
|
+
def simple_run_command(self, args, **kw):
|
|
13
|
+
sbp.run(args, **(sbp_run_pipe_check_kw | kw))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@attr.s(eq=False, hash=False)
|
|
17
|
+
class Module:
|
|
18
|
+
utility: Utility = attr.ib(repr=False)
|
|
19
|
+
|
|
20
|
+
REGISTRY = {}
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def _make_kwargs(cls, output, kw):
|
|
24
|
+
output["utility"] = kw["utility"]
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_config(cls, **kw):
|
|
28
|
+
cls._make_kwargs((cls_kw := {}), kw)
|
|
29
|
+
return cls(**cls_kw)
|
|
30
|
+
|
|
31
|
+
def __init_subclass__(cls, **kwargs):
|
|
32
|
+
super().__init_subclass__(**kwargs)
|
|
33
|
+
cls.REGISTRY[cls.__name__] = cls
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@attr.s(eq=False, hash=False)
|
|
37
|
+
class ModuleKitty(Module):
|
|
38
|
+
config_key = "kitty"
|
|
39
|
+
p_theme_name: str = attr.ib()
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def _make_kwargs(cls, output, kw):
|
|
43
|
+
super()._make_kwargs(output, kw)
|
|
44
|
+
output.update(p_theme_name=kw["config"]["theme_name"])
|
|
45
|
+
|
|
46
|
+
def run(self):
|
|
47
|
+
self.utility.simple_run_command(["kitty", "+kitten", "themes", "--cache-age=-1", self.p_theme_name])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@attr.s(eq=False, hash=False)
|
|
51
|
+
class ModuleEmacsClient(Module):
|
|
52
|
+
config_key = "emacsclient"
|
|
53
|
+
p_theme_names: list[str] = attr.ib()
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def _make_kwargs(cls, output, kw):
|
|
57
|
+
super()._make_kwargs(output, kw)
|
|
58
|
+
output.update(p_theme_names=kw["config"]["theme_names"])
|
|
59
|
+
|
|
60
|
+
def run(self):
|
|
61
|
+
rx_safe = re.compile("^[a-zA-Z0-9_+/-]+$")
|
|
62
|
+
for name in self.p_theme_names:
|
|
63
|
+
if not rx_safe.search(name):
|
|
64
|
+
raise ValueError(f"unsafe name: {name!r}")
|
|
65
|
+
self.utility.simple_run_command(
|
|
66
|
+
["emacsclient", "-n", "-r", "--eval", f"(set-custom-enable-themes '({' '.join(self.p_theme_names)}))"]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@attr.s(eq=False, hash=False)
|
|
71
|
+
class ModuleKDE(Module):
|
|
72
|
+
config_key = "kde"
|
|
73
|
+
p_color_scheme: str = attr.ib()
|
|
74
|
+
p_desktop_theme: str = attr.ib()
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def _make_kwargs(cls, output, kw):
|
|
78
|
+
super()._make_kwargs(output, kw)
|
|
79
|
+
output.update(p_color_scheme=kw["config"]["plasma_color_scheme"])
|
|
80
|
+
output.update(p_desktop_theme=kw["config"]["plasma_desktop_theme"])
|
|
81
|
+
|
|
82
|
+
def run(self):
|
|
83
|
+
self.utility.simple_run_command(["plasma-apply-desktoptheme", self.p_desktop_theme])
|
|
84
|
+
self.utility.simple_run_command(["plasma-apply-colorscheme", self.p_color_scheme])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@attr.s(eq=False, hash=False)
|
|
88
|
+
class ModuleLyx(Module):
|
|
89
|
+
config_key = "lyx"
|
|
90
|
+
p_color_section: str = attr.ib()
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def _make_kwargs(cls, output, kw):
|
|
94
|
+
super()._make_kwargs(output, kw)
|
|
95
|
+
output.update(p_color_section=kw["config"]["preferences_color_section"])
|
|
96
|
+
|
|
97
|
+
def run(self):
|
|
98
|
+
p = Path.home() / ".lyx/preferences"
|
|
99
|
+
repl = "".join(("\n", self.p_color_section.strip(), "\n\n"))
|
|
100
|
+
out = re.sub(
|
|
101
|
+
"^(# COLOR SECTION #+\n#\n).*?(?=^#\n)",
|
|
102
|
+
lambda m: "".join([m.group(1), repl]),
|
|
103
|
+
p.read_text(encoding="utf-8"),
|
|
104
|
+
count=1,
|
|
105
|
+
flags=re.M + re.S,
|
|
106
|
+
)
|
|
107
|
+
with atomicwrites.atomic_write(str(p), mode="wt") as fh:
|
|
108
|
+
fh.write(out)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@attr.s(eq=False, hash=False)
|
|
112
|
+
class ModuleGTK(Module):
|
|
113
|
+
config_key = "gtk2"
|
|
114
|
+
p_gtk_theme: str = attr.ib()
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def _make_kwargs(cls, output, kw):
|
|
118
|
+
super()._make_kwargs(output, kw)
|
|
119
|
+
output.update(p_theme=kw["config"]["theme_name"])
|
|
120
|
+
|
|
121
|
+
def run(self):
|
|
122
|
+
(Path.home() / ".gtkrc-2.0").delete()
|
|
123
|
+
self.utility.simple_run_command(
|
|
124
|
+
[
|
|
125
|
+
"xfconf-query" "-c",
|
|
126
|
+
"xsettings",
|
|
127
|
+
"-p",
|
|
128
|
+
"/Net/ThemeName",
|
|
129
|
+
"-t",
|
|
130
|
+
"string",
|
|
131
|
+
"-s",
|
|
132
|
+
self.p_theme,
|
|
133
|
+
],
|
|
134
|
+
)
|
lightdark/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lightdark
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Cycle between your desktop themes (such as light and dark)
|
|
5
|
+
Author-email: Eduard Christian Dumitrescu <eduard.c.dumitrescu@gmail.com>
|
|
6
|
+
Maintainer-email: Eduard Christian Dumitrescu <eduard.c.dumitrescu@gmail.com>
|
|
7
|
+
License: Apache Software License 2.0
|
|
8
|
+
Project-URL: Homepage, https://hydra.ecd.space/eduard/darklight/timeline
|
|
9
|
+
Project-URL: Changelog, https://hydra.ecd.space/eduard/darklight/file?name=CHANGELOG.md&ci=trunk
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
License-File: AUTHORS.md
|
|
14
|
+
Requires-Dist: attrs
|
|
15
|
+
Requires-Dist: atomicwrites
|
|
16
|
+
Requires-Dist: mergedeep
|
|
17
|
+
Requires-Dist: platformdirs
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# lightdark
|
|
21
|
+
|
|
22
|
+
## Quick start guide
|
|
23
|
+
|
|
24
|
+
Copy `config.json.example` to `~/.config/lightdark/config.json` then modify it to your liking. Then you can just run
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
lightdark
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
and it will cycle through the themes for you. Try `lightdark --help` to see more options.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
lightdark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
lightdark/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
|
|
3
|
+
lightdark/main.py,sha256=ZaFLvyClsXovv-V4p0qk4iwhT-eNw5Pque11YQtrFcM,2668
|
|
4
|
+
lightdark/modules.py,sha256=qsmW1fXsadH1vXUVl0Gqu3uOonKLBU7XH6CY0ZNucZM,3825
|
|
5
|
+
lightdark/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
lightdark-0.9.0.dist-info/licenses/AUTHORS.md,sha256=sXkm88GaYJ63k0Hy7UlGboAT4aytU8e1_K0wNo7WwD8,144
|
|
7
|
+
lightdark-0.9.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
lightdark-0.9.0.dist-info/METADATA,sha256=5fuiqDwTT7QhN4XU1YBod9ZSGC-9YvPNCupB-pR2rOU,1004
|
|
9
|
+
lightdark-0.9.0.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
|
10
|
+
lightdark-0.9.0.dist-info/entry_points.txt,sha256=Hk9I0f-ccY2Hqw2TNGW9kcVZaCa2FLtA0ms5gIHNmAM,50
|
|
11
|
+
lightdark-0.9.0.dist-info/top_level.txt,sha256=XGZEM1hC63FWTf1fxJNd9-1PWUopBN0CNKkdneb8xpQ,10
|
|
12
|
+
lightdark-0.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lightdark
|