termux-colors 0.1.0__tar.gz
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.
- termux_colors-0.1.0/PKG-INFO +92 -0
- termux_colors-0.1.0/README.md +79 -0
- termux_colors-0.1.0/pyproject.toml +31 -0
- termux_colors-0.1.0/setup.cfg +4 -0
- termux_colors-0.1.0/src/termux_colors/__init__.py +3 -0
- termux_colors-0.1.0/src/termux_colors/__main__.py +4 -0
- termux_colors-0.1.0/src/termux_colors/app.py +244 -0
- termux_colors-0.1.0/src/termux_colors/library.py +114 -0
- termux_colors-0.1.0/src/termux_colors/models/__init__.py +0 -0
- termux_colors-0.1.0/src/termux_colors/models/app_theme.py +137 -0
- termux_colors-0.1.0/src/termux_colors/models/colors.py +121 -0
- termux_colors-0.1.0/src/termux_colors/paths.py +38 -0
- termux_colors-0.1.0/src/termux_colors/persistence/__init__.py +0 -0
- termux_colors-0.1.0/src/termux_colors/persistence/backup.py +40 -0
- termux_colors-0.1.0/src/termux_colors/persistence/colors_io.py +74 -0
- termux_colors-0.1.0/src/termux_colors/persistence/properties.py +75 -0
- termux_colors-0.1.0/src/termux_colors/persistence/theme_io.py +44 -0
- termux_colors-0.1.0/src/termux_colors/presets/__init__.py +0 -0
- termux_colors-0.1.0/src/termux_colors/presets/built_in.py +95 -0
- termux_colors-0.1.0/src/termux_colors/screens/__init__.py +0 -0
- termux_colors-0.1.0/src/termux_colors/screens/app_theme_editor.py +150 -0
- termux_colors-0.1.0/src/termux_colors/screens/dialogs.py +127 -0
- termux_colors-0.1.0/src/termux_colors/screens/main_menu.py +79 -0
- termux_colors-0.1.0/src/termux_colors/screens/preset_picker.py +234 -0
- termux_colors-0.1.0/src/termux_colors/screens/terminal_editor.py +229 -0
- termux_colors-0.1.0/src/termux_colors/theme.py +135 -0
- termux_colors-0.1.0/src/termux_colors/widgets.py +179 -0
- termux_colors-0.1.0/src/termux_colors.egg-info/PKG-INFO +92 -0
- termux_colors-0.1.0/src/termux_colors.egg-info/SOURCES.txt +37 -0
- termux_colors-0.1.0/src/termux_colors.egg-info/dependency_links.txt +1 -0
- termux_colors-0.1.0/src/termux_colors.egg-info/entry_points.txt +2 -0
- termux_colors-0.1.0/src/termux_colors.egg-info/requires.txt +4 -0
- termux_colors-0.1.0/src/termux_colors.egg-info/top_level.txt +1 -0
- termux_colors-0.1.0/tests/test_app_smoke.py +93 -0
- termux_colors-0.1.0/tests/test_backup.py +50 -0
- termux_colors-0.1.0/tests/test_colors_io.py +80 -0
- termux_colors-0.1.0/tests/test_library.py +105 -0
- termux_colors-0.1.0/tests/test_theme.py +93 -0
- termux_colors-0.1.0/tests/test_theme_io.py +99 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: termux-colors
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Textual TUI to change Termux terminal colors and app theme
|
|
5
|
+
Author-email: Ron Sloan <xkiller1177@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/GeneralKaos666/color-changer-termux
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: textual>=0.60
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# termux-colors
|
|
15
|
+
|
|
16
|
+
A keyboard-driven TUI for [Termux](https://termux.dev) that changes your
|
|
17
|
+
terminal colour scheme and your Termux app theme.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
┌─────────────────────────────┐
|
|
21
|
+
│ Termux Color Changer │
|
|
22
|
+
│ [ Preset Themes ] │
|
|
23
|
+
│ [ Terminal Colors ] │
|
|
24
|
+
│ [ App Theme ] │
|
|
25
|
+
│ [ Quit ] │
|
|
26
|
+
└─────────────────────────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Themes itself from your palette** — the TUI re-colours itself to match the
|
|
32
|
+
active terminal theme, and updates live as you highlight presets.
|
|
33
|
+
- **Preset Themes** — browses the themes in `~/.termux/colors/` (the same
|
|
34
|
+
library used by `colors.sh`), with a filter box, a mock shell session
|
|
35
|
+
rendered in each theme, a swatch grid, and a colour strip. Activating a theme
|
|
36
|
+
points `~/.termux/colors.properties` at it and runs `termux-reload-settings`.
|
|
37
|
+
- **Terminal Colors** — edits background, foreground, cursor and the 16 ANSI
|
|
38
|
+
colours with inline swatches, live validation and a live preview. Save in
|
|
39
|
+
place or `Save As...` a new theme.
|
|
40
|
+
- **App Theme** — edits a safe subset of `~/.termux/termux.properties`
|
|
41
|
+
(`use-black-ui`, cursor style, margins, transcript rows, etc.) in sectioned
|
|
42
|
+
groups, without touching anything it does not manage.
|
|
43
|
+
- **Safe by default** — before overwriting an existing config it asks whether
|
|
44
|
+
to write a timestamped backup to `~/.termux/backups/`.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
Requires Python 3.10+ (Termux: `pkg install python`).
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
pip install .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This installs a `termux-colors` command on your `PATH`.
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
termux-colors
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Navigation: `Tab`/`Shift+Tab` moves focus, `Enter` activates a button,
|
|
63
|
+
`Esc` goes back, `q` quits from the main menu. In Preset Themes, focus the
|
|
64
|
+
filter box and type to search the library.
|
|
65
|
+
|
|
66
|
+
## Relationship to `colors.sh`
|
|
67
|
+
|
|
68
|
+
`termux-colors` reads the same library as `~/.termux/colors.sh`
|
|
69
|
+
(`~/.termux/colors/<group>/*.properties`) and activates themes the same way:
|
|
70
|
+
by symlinking `~/.termux/colors.properties` and calling
|
|
71
|
+
`termux-reload-settings`. The two can be used interchangeably. If the library
|
|
72
|
+
is empty, a small set of built-in themes is written to
|
|
73
|
+
`~/.termux/colors/builtin/`.
|
|
74
|
+
|
|
75
|
+
`termux.properties` is edited line-by-line, so complex settings such as
|
|
76
|
+
`extra-keys`, background images and comments are left untouched.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
python -m venv .venv
|
|
82
|
+
.venv/bin/pip install -e '.[dev]'
|
|
83
|
+
.venv/bin/python -m pytest
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Tests point the app at a temporary directory through the `TERMUX_HOME`
|
|
87
|
+
environment variable, so they never touch your real config.
|
|
88
|
+
|
|
89
|
+
## Backups
|
|
90
|
+
|
|
91
|
+
Backups are plain copies named `colors-YYYY-MM-DD-HHMMSS.properties` or
|
|
92
|
+
`termux-YYYY-MM-DD-HHMMSS.properties` under `~/.termux/backups/`.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# termux-colors
|
|
2
|
+
|
|
3
|
+
A keyboard-driven TUI for [Termux](https://termux.dev) that changes your
|
|
4
|
+
terminal colour scheme and your Termux app theme.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
┌─────────────────────────────┐
|
|
8
|
+
│ Termux Color Changer │
|
|
9
|
+
│ [ Preset Themes ] │
|
|
10
|
+
│ [ Terminal Colors ] │
|
|
11
|
+
│ [ App Theme ] │
|
|
12
|
+
│ [ Quit ] │
|
|
13
|
+
└─────────────────────────────┘
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Themes itself from your palette** — the TUI re-colours itself to match the
|
|
19
|
+
active terminal theme, and updates live as you highlight presets.
|
|
20
|
+
- **Preset Themes** — browses the themes in `~/.termux/colors/` (the same
|
|
21
|
+
library used by `colors.sh`), with a filter box, a mock shell session
|
|
22
|
+
rendered in each theme, a swatch grid, and a colour strip. Activating a theme
|
|
23
|
+
points `~/.termux/colors.properties` at it and runs `termux-reload-settings`.
|
|
24
|
+
- **Terminal Colors** — edits background, foreground, cursor and the 16 ANSI
|
|
25
|
+
colours with inline swatches, live validation and a live preview. Save in
|
|
26
|
+
place or `Save As...` a new theme.
|
|
27
|
+
- **App Theme** — edits a safe subset of `~/.termux/termux.properties`
|
|
28
|
+
(`use-black-ui`, cursor style, margins, transcript rows, etc.) in sectioned
|
|
29
|
+
groups, without touching anything it does not manage.
|
|
30
|
+
- **Safe by default** — before overwriting an existing config it asks whether
|
|
31
|
+
to write a timestamped backup to `~/.termux/backups/`.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
Requires Python 3.10+ (Termux: `pkg install python`).
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
pip install .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This installs a `termux-colors` command on your `PATH`.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
termux-colors
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Navigation: `Tab`/`Shift+Tab` moves focus, `Enter` activates a button,
|
|
50
|
+
`Esc` goes back, `q` quits from the main menu. In Preset Themes, focus the
|
|
51
|
+
filter box and type to search the library.
|
|
52
|
+
|
|
53
|
+
## Relationship to `colors.sh`
|
|
54
|
+
|
|
55
|
+
`termux-colors` reads the same library as `~/.termux/colors.sh`
|
|
56
|
+
(`~/.termux/colors/<group>/*.properties`) and activates themes the same way:
|
|
57
|
+
by symlinking `~/.termux/colors.properties` and calling
|
|
58
|
+
`termux-reload-settings`. The two can be used interchangeably. If the library
|
|
59
|
+
is empty, a small set of built-in themes is written to
|
|
60
|
+
`~/.termux/colors/builtin/`.
|
|
61
|
+
|
|
62
|
+
`termux.properties` is edited line-by-line, so complex settings such as
|
|
63
|
+
`extra-keys`, background images and comments are left untouched.
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
python -m venv .venv
|
|
69
|
+
.venv/bin/pip install -e '.[dev]'
|
|
70
|
+
.venv/bin/python -m pytest
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Tests point the app at a temporary directory through the `TERMUX_HOME`
|
|
74
|
+
environment variable, so they never touch your real config.
|
|
75
|
+
|
|
76
|
+
## Backups
|
|
77
|
+
|
|
78
|
+
Backups are plain copies named `colors-YYYY-MM-DD-HHMMSS.properties` or
|
|
79
|
+
`termux-YYYY-MM-DD-HHMMSS.properties` under `~/.termux/backups/`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "termux-colors"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Textual TUI to change Termux terminal colors and app theme"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name="Ron Sloan", email="xkiller1177@gmail.com" },
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
dependencies = ["textual>=0.60"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
"Homepage" = "https://github.com/GeneralKaos666/color-changer-termux"
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
dev = ["pytest>=7"]
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
termux-colors = "termux_colors.app:main"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
testpaths = ["tests"]
|
|
31
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Textual application entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textual.app import App
|
|
6
|
+
|
|
7
|
+
from .library import active_colors
|
|
8
|
+
from .screens.app_theme_editor import AppThemeEditorScreen
|
|
9
|
+
from .screens.main_menu import MainMenuScreen
|
|
10
|
+
from .screens.preset_picker import PresetPickerScreen
|
|
11
|
+
from .screens.terminal_editor import TerminalEditorScreen
|
|
12
|
+
from .theme import apply_app_theme
|
|
13
|
+
|
|
14
|
+
CSS = """
|
|
15
|
+
Screen {
|
|
16
|
+
background: $background;
|
|
17
|
+
color: $foreground;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Header {
|
|
21
|
+
background: $panel;
|
|
22
|
+
color: $foreground;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Footer {
|
|
26
|
+
background: $panel;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#menu-container {
|
|
30
|
+
align: center top;
|
|
31
|
+
padding: 1 2;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#banner {
|
|
35
|
+
text-align: center;
|
|
36
|
+
width: 100%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#tagline {
|
|
40
|
+
text-align: center;
|
|
41
|
+
color: $text-muted;
|
|
42
|
+
width: 100%;
|
|
43
|
+
padding-bottom: 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#strip {
|
|
47
|
+
text-align: center;
|
|
48
|
+
width: 100%;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#status {
|
|
52
|
+
text-align: center;
|
|
53
|
+
color: $text-muted;
|
|
54
|
+
width: 100%;
|
|
55
|
+
padding: 1 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#menu {
|
|
59
|
+
width: 62;
|
|
60
|
+
max-width: 100%;
|
|
61
|
+
height: auto;
|
|
62
|
+
border: round $primary;
|
|
63
|
+
border-title-color: $primary;
|
|
64
|
+
border-title-align: center;
|
|
65
|
+
padding: 1 2;
|
|
66
|
+
background: $surface;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#menu Button {
|
|
70
|
+
width: 100%;
|
|
71
|
+
margin-bottom: 1;
|
|
72
|
+
text-style: bold;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.panel-title {
|
|
76
|
+
text-style: bold;
|
|
77
|
+
background: $boost;
|
|
78
|
+
color: $foreground;
|
|
79
|
+
width: 100%;
|
|
80
|
+
padding: 0 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.hint {
|
|
84
|
+
color: $text-muted;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#picker {
|
|
88
|
+
height: 1fr;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#picker-list {
|
|
92
|
+
width: 42%;
|
|
93
|
+
min-width: 26;
|
|
94
|
+
border: round $primary;
|
|
95
|
+
border-title-color: $primary;
|
|
96
|
+
padding: 0 1;
|
|
97
|
+
background: $surface;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#theme-filter {
|
|
101
|
+
margin: 1 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#picker-preview {
|
|
105
|
+
width: 58%;
|
|
106
|
+
border: round $secondary;
|
|
107
|
+
border-title-color: $secondary;
|
|
108
|
+
padding: 1 2;
|
|
109
|
+
background: $surface;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#details {
|
|
113
|
+
color: $text-muted;
|
|
114
|
+
padding-top: 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#editor {
|
|
118
|
+
height: 1fr;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#editor-form {
|
|
122
|
+
width: 52%;
|
|
123
|
+
min-width: 32;
|
|
124
|
+
border: round $primary;
|
|
125
|
+
border-title-color: $primary;
|
|
126
|
+
padding: 0 1 1 1;
|
|
127
|
+
background: $surface;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#editor-preview {
|
|
131
|
+
width: 48%;
|
|
132
|
+
border: round $secondary;
|
|
133
|
+
border-title-color: $secondary;
|
|
134
|
+
padding: 1 2;
|
|
135
|
+
background: $surface;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.field-label {
|
|
139
|
+
color: $text-muted;
|
|
140
|
+
text-style: bold;
|
|
141
|
+
padding-top: 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.field-row {
|
|
145
|
+
height: auto;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.field-row Input {
|
|
149
|
+
width: 1fr;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.field-swatch {
|
|
153
|
+
width: 4;
|
|
154
|
+
height: 1;
|
|
155
|
+
margin: 0 1 0 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.input-error {
|
|
159
|
+
border: tall $error;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.section-rule {
|
|
163
|
+
color: $accent;
|
|
164
|
+
padding-top: 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#actions {
|
|
168
|
+
height: auto;
|
|
169
|
+
padding: 1 0;
|
|
170
|
+
align-horizontal: center;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#actions Button {
|
|
174
|
+
margin: 0 1;
|
|
175
|
+
min-width: 12;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ConfirmScreen, MessageScreen, PromptScreen {
|
|
179
|
+
align: center middle;
|
|
180
|
+
background: $background 70%;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#dialog {
|
|
184
|
+
width: 64;
|
|
185
|
+
max-width: 90%;
|
|
186
|
+
height: auto;
|
|
187
|
+
padding: 1 2;
|
|
188
|
+
background: $panel;
|
|
189
|
+
border: round $primary;
|
|
190
|
+
border-title-color: $primary;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#dialog-title {
|
|
194
|
+
text-style: bold;
|
|
195
|
+
color: $primary;
|
|
196
|
+
padding-bottom: 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#dialog-message {
|
|
200
|
+
padding-bottom: 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#dialog-buttons {
|
|
204
|
+
height: auto;
|
|
205
|
+
align-horizontal: right;
|
|
206
|
+
padding-top: 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#dialog-buttons Button {
|
|
210
|
+
margin-left: 1;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
Toast {
|
|
214
|
+
background: $panel;
|
|
215
|
+
border: round $primary;
|
|
216
|
+
color: $foreground;
|
|
217
|
+
}
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TermuxColorsApp(App):
|
|
222
|
+
"""Manage Termux terminal colours and app theme."""
|
|
223
|
+
|
|
224
|
+
CSS = CSS
|
|
225
|
+
TITLE = "Termux Colors"
|
|
226
|
+
SCREENS = {
|
|
227
|
+
"main": MainMenuScreen,
|
|
228
|
+
"presets": PresetPickerScreen,
|
|
229
|
+
"terminal": TerminalEditorScreen,
|
|
230
|
+
"app-theme": AppThemeEditorScreen,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
def on_mount(self) -> None:
|
|
234
|
+
apply_app_theme(self, active_colors())
|
|
235
|
+
self.push_screen("main")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def main() -> None:
|
|
239
|
+
"""Console-script entry point."""
|
|
240
|
+
TermuxColorsApp().run()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
main()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Scan the user's theme library and activate themes by symlink."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Theme:
|
|
14
|
+
"""One theme file discovered in the colour library."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
path: Path
|
|
18
|
+
group: str = "root"
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def label(self) -> str:
|
|
22
|
+
if self.group and self.group != "root":
|
|
23
|
+
return f"{self.group}/{self.name}"
|
|
24
|
+
return self.name
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _is_backup(path: Path) -> bool:
|
|
28
|
+
name = path.name.lower()
|
|
29
|
+
return "backup" in name or name.endswith(".bak") or ".bak-" in name
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def scan_themes(root: Path) -> list[Theme]:
|
|
33
|
+
"""Find every usable ``*.properties`` theme under *root*.
|
|
34
|
+
|
|
35
|
+
The active ``colors.properties`` file, backups, and dotfiles are skipped.
|
|
36
|
+
Results are ordered by group then name.
|
|
37
|
+
"""
|
|
38
|
+
if not root.is_dir():
|
|
39
|
+
return []
|
|
40
|
+
themes: list[Theme] = []
|
|
41
|
+
for path in root.rglob("*.properties"):
|
|
42
|
+
if not path.is_file() and not path.is_symlink():
|
|
43
|
+
continue
|
|
44
|
+
if path.name == "colors.properties" or _is_backup(path):
|
|
45
|
+
continue
|
|
46
|
+
relative = path.relative_to(root)
|
|
47
|
+
group = relative.parts[0] if len(relative.parts) > 1 else "root"
|
|
48
|
+
if group.startswith("."):
|
|
49
|
+
continue
|
|
50
|
+
themes.append(Theme(name=path.stem, path=path, group=group))
|
|
51
|
+
themes.sort(key=lambda theme: ((theme.group == "root"), theme.group.lower(), theme.name.lower()))
|
|
52
|
+
return themes
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def group_themes(themes: list[Theme]) -> dict[str, list[Theme]]:
|
|
56
|
+
"""Group themes by their library sub-directory."""
|
|
57
|
+
grouped: dict[str, list[Theme]] = {}
|
|
58
|
+
for theme in themes:
|
|
59
|
+
grouped.setdefault(theme.group, []).append(theme)
|
|
60
|
+
return grouped
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def active_theme_path(colors_properties: Path) -> Path | None:
|
|
64
|
+
"""Return the real theme file the active ``colors.properties`` resolves to."""
|
|
65
|
+
if not colors_properties.exists() and not colors_properties.is_symlink():
|
|
66
|
+
return None
|
|
67
|
+
return Path(os.path.realpath(colors_properties))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def active_colors():
|
|
71
|
+
"""Return the colours currently configured, or the defaults when unset."""
|
|
72
|
+
from .models.colors import TerminalColors
|
|
73
|
+
from .paths import colors_properties_path
|
|
74
|
+
from .persistence.colors_io import read_colors
|
|
75
|
+
|
|
76
|
+
path = colors_properties_path()
|
|
77
|
+
if path.exists() or path.is_symlink():
|
|
78
|
+
try:
|
|
79
|
+
return read_colors(path)
|
|
80
|
+
except OSError:
|
|
81
|
+
pass
|
|
82
|
+
return TerminalColors()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def apply_theme(theme_path: Path, colors_properties: Path) -> None:
|
|
86
|
+
"""Point ``colors.properties`` at *theme_path* and reload Termux.
|
|
87
|
+
|
|
88
|
+
An existing file or symlink is replaced. ``colors.properties`` becomes a
|
|
89
|
+
symlink into the library, matching the layout used by ``colors.sh``. If
|
|
90
|
+
the target already is ``colors.properties`` itself (a plain file the user
|
|
91
|
+
edits in place) this is a no-op so no self-referential link is created.
|
|
92
|
+
"""
|
|
93
|
+
theme_path = theme_path.resolve()
|
|
94
|
+
if not theme_path.is_file():
|
|
95
|
+
raise FileNotFoundError(f"theme not found: {theme_path}")
|
|
96
|
+
if colors_properties.exists() or colors_properties.is_symlink():
|
|
97
|
+
if colors_properties.resolve() == theme_path:
|
|
98
|
+
return
|
|
99
|
+
colors_properties.parent.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
if colors_properties.is_symlink() or colors_properties.exists():
|
|
101
|
+
colors_properties.unlink()
|
|
102
|
+
os.symlink(theme_path, colors_properties)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def reload_termux() -> bool:
|
|
106
|
+
"""Run ``termux-reload-settings`` when available; return success."""
|
|
107
|
+
executable = shutil.which("termux-reload-settings")
|
|
108
|
+
if not executable:
|
|
109
|
+
return False
|
|
110
|
+
try:
|
|
111
|
+
subprocess.run([executable], check=True, timeout=15)
|
|
112
|
+
except (subprocess.SubprocessError, OSError):
|
|
113
|
+
return False
|
|
114
|
+
return True
|
|
File without changes
|