vappman 2.2.2__tar.gz → 2.2.4__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.
- {vappman-2.2.2 → vappman-2.2.4}/PKG-INFO +8 -6
- {vappman-2.2.2 → vappman-2.2.4}/README.md +4 -4
- {vappman-2.2.2 → vappman-2.2.4}/pyproject.toml +14 -2
- {vappman-2.2.2 → vappman-2.2.4}/vappman/Appearance.py +28 -20
- {vappman-2.2.2 → vappman-2.2.4}/vappman/AppimageDoctor.py +15 -4
- {vappman-2.2.2 → vappman-2.2.4}/vappman/Prerequisites.py +20 -7
- {vappman-2.2.2 → vappman-2.2.4}/vappman/__init__.py +1 -1
- {vappman-2.2.2 → vappman-2.2.4}/vappman/main.py +71 -42
- {vappman-2.2.2 → vappman-2.2.4}/LICENSE +0 -0
- {vappman-2.2.2 → vappman-2.2.4}/vappman/AppmanLauncher.py +0 -0
- {vappman-2.2.2 → vappman-2.2.4}/vappman/AppmanVars.py +0 -0
- {vappman-2.2.2 → vappman-2.2.4}/vappman/CommandRunner.py +0 -0
- {vappman-2.2.2 → vappman-2.2.4}/vappman/PersistentState.py +0 -0
- {vappman-2.2.2 → vappman-2.2.4}/vappman/VappmanListCache.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vappman
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
4
4
|
Summary: A visual wrapper for appman
|
|
5
5
|
Keywords: app,installer,manager,appimages
|
|
6
6
|
Author-email: Joe Defen <joedef@google.com>
|
|
@@ -15,14 +15,16 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Operating System :: POSIX :: Linux
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: console-window
|
|
18
|
+
Requires-Dist: console-window ~= 3.0.0
|
|
19
19
|
Requires-Dist: pytest>=7 ; extra == "dev"
|
|
20
20
|
Requires-Dist: pytest-cov>=5 ; extra == "dev"
|
|
21
21
|
Requires-Dist: ruff>=0.6 ; extra == "dev"
|
|
22
22
|
Requires-Dist: build>=1.2 ; extra == "dev"
|
|
23
23
|
Requires-Dist: twine>=7 ; extra == "dev"
|
|
24
24
|
Requires-Dist: packaging>=26 ; extra == "dev"
|
|
25
|
+
Requires-Dist: tomli>=2 ; extra == "dev" and ( python_version < '3.11')
|
|
25
26
|
Requires-Dist: pytest>=7.0 ; extra == "test"
|
|
27
|
+
Requires-Dist: tomli>=2 ; extra == "test" and ( python_version < '3.11')
|
|
26
28
|
Project-URL: Bug Tracker, https://github.com/joedefen/vappman/issues
|
|
27
29
|
Project-URL: Homepage, https://github.com/joedefen/vappman
|
|
28
30
|
Provides-Extra: dev
|
|
@@ -196,7 +198,7 @@ Press `/` to start filtering. The filter supports two modes:
|
|
|
196
198
|
* `i` or `ENTER` - Install the app. A prompt appears *below* the entry's rows
|
|
197
199
|
(so the synopsis stays readable), followed by the install-type menu:
|
|
198
200
|
```
|
|
199
|
-
|
|
201
|
+
🌟 Install <app>?
|
|
200
202
|
*n:none s:sandbox c:icons b:both y:last ESC
|
|
201
203
|
```
|
|
202
204
|
* `n` - plain install
|
|
@@ -221,7 +223,7 @@ Press `/` to start filtering. The filter supports two modes:
|
|
|
221
223
|
* `t` - Test the app by running it in a terminal
|
|
222
224
|
|
|
223
225
|
For `r`, `C`, `S` and `b`, a prompt appears below the entry's rows (synopsis and
|
|
224
|
-
version/type remain visible), e.g.
|
|
226
|
+
version/type remain visible), e.g. `🌟 Remove <app>? Press y or ESC`; `y`
|
|
225
227
|
confirms, `ESC` cancels, and all other keys are ignored until you answer. The
|
|
226
228
|
confirmed command is run with `-y`, so `am/appman` does not ask again.
|
|
227
229
|
|
|
@@ -289,7 +291,7 @@ AppImage Compatibility Check (Ubuntu 24.04 LTS)
|
|
|
289
291
|
============================================================
|
|
290
292
|
✗ libfuse2: libfuse2 is not installed (required for most AppImages)
|
|
291
293
|
Fix: sudo apt install libfuse2
|
|
292
|
-
|
|
294
|
+
🌟 user namespaces: Unprivileged user namespaces are disabled
|
|
293
295
|
✓ /tmp noexec: OK
|
|
294
296
|
✓ AppArmor: OK
|
|
295
297
|
|
|
@@ -300,7 +302,7 @@ Detailed Information:
|
|
|
300
302
|
libfuse2 is not installed (required for most AppImages)
|
|
301
303
|
Fix: sudo apt install libfuse2
|
|
302
304
|
|
|
303
|
-
|
|
305
|
+
🌟 user_namespaces:
|
|
304
306
|
Unprivileged user namespaces are disabled (sandboxed AppImages will fail)
|
|
305
307
|
Suggestion: sudo sysctl -w kernel.unprivileged_userns_clone=1
|
|
306
308
|
To make permanent: echo "kernel.unprivileged_userns_clone=1" | sudo tee /etc/sysctl.d/00-local-userns.conf
|
|
@@ -165,7 +165,7 @@ Press `/` to start filtering. The filter supports two modes:
|
|
|
165
165
|
* `i` or `ENTER` - Install the app. A prompt appears *below* the entry's rows
|
|
166
166
|
(so the synopsis stays readable), followed by the install-type menu:
|
|
167
167
|
```
|
|
168
|
-
|
|
168
|
+
🌟 Install <app>?
|
|
169
169
|
*n:none s:sandbox c:icons b:both y:last ESC
|
|
170
170
|
```
|
|
171
171
|
* `n` - plain install
|
|
@@ -190,7 +190,7 @@ Press `/` to start filtering. The filter supports two modes:
|
|
|
190
190
|
* `t` - Test the app by running it in a terminal
|
|
191
191
|
|
|
192
192
|
For `r`, `C`, `S` and `b`, a prompt appears below the entry's rows (synopsis and
|
|
193
|
-
version/type remain visible), e.g.
|
|
193
|
+
version/type remain visible), e.g. `🌟 Remove <app>? Press y or ESC`; `y`
|
|
194
194
|
confirms, `ESC` cancels, and all other keys are ignored until you answer. The
|
|
195
195
|
confirmed command is run with `-y`, so `am/appman` does not ask again.
|
|
196
196
|
|
|
@@ -258,7 +258,7 @@ AppImage Compatibility Check (Ubuntu 24.04 LTS)
|
|
|
258
258
|
============================================================
|
|
259
259
|
✗ libfuse2: libfuse2 is not installed (required for most AppImages)
|
|
260
260
|
Fix: sudo apt install libfuse2
|
|
261
|
-
|
|
261
|
+
🌟 user namespaces: Unprivileged user namespaces are disabled
|
|
262
262
|
✓ /tmp noexec: OK
|
|
263
263
|
✓ AppArmor: OK
|
|
264
264
|
|
|
@@ -269,7 +269,7 @@ Detailed Information:
|
|
|
269
269
|
libfuse2 is not installed (required for most AppImages)
|
|
270
270
|
Fix: sudo apt install libfuse2
|
|
271
271
|
|
|
272
|
-
|
|
272
|
+
🌟 user_namespaces:
|
|
273
273
|
Unprivileged user namespaces are disabled (sandboxed AppImages will fail)
|
|
274
274
|
Suggestion: sudo sysctl -w kernel.unprivileged_userns_clone=1
|
|
275
275
|
To make permanent: echo "kernel.unprivileged_userns_clone=1" | sudo tee /etc/sysctl.d/00-local-userns.conf
|
|
@@ -32,7 +32,12 @@ classifiers = [
|
|
|
32
32
|
"Operating System :: POSIX :: Linux"
|
|
33
33
|
]
|
|
34
34
|
dependencies = [
|
|
35
|
-
|
|
35
|
+
# 3.0 is the first release whose content model lives on Screen (one drawing
|
|
36
|
+
# model) and whose appearance layer is ``console_window.Appearance``. The
|
|
37
|
+
# compatible-3.0.x spelling keeps the app on the 3.0 line: patch releases
|
|
38
|
+
# are fixes, while 3.1 may add (not break) API and would be a deliberate
|
|
39
|
+
# move.
|
|
40
|
+
'console-window ~= 3.0.0',
|
|
36
41
|
]
|
|
37
42
|
|
|
38
43
|
[project.urls]
|
|
@@ -43,7 +48,13 @@ dependencies = [
|
|
|
43
48
|
vappman = "vappman.main:main"
|
|
44
49
|
|
|
45
50
|
[project.optional-dependencies]
|
|
46
|
-
|
|
51
|
+
# `tomli` backfills `tomllib` on 3.10, where `tests/test_bless.py` and
|
|
52
|
+
# `tests/inspect_dist.py` read pyproject.toml. The vendored `bless` already
|
|
53
|
+
# falls back to it, so the gate and its tests carry one requirement.
|
|
54
|
+
test = [
|
|
55
|
+
"pytest>=7.0",
|
|
56
|
+
"tomli>=2; python_version < '3.11'",
|
|
57
|
+
]
|
|
47
58
|
# The release gate (`./bless`) runs ruff, compileall and pytest with coverage,
|
|
48
59
|
# then builds, inspects, `twine check`s and smoke-installs the artifacts. Those
|
|
49
60
|
# tools must live in the interpreter that runs bless, and twine/packaging must
|
|
@@ -55,6 +66,7 @@ dev = [
|
|
|
55
66
|
"build>=1.2",
|
|
56
67
|
"twine>=7",
|
|
57
68
|
"packaging>=26",
|
|
69
|
+
"tomli>=2; python_version < '3.11'",
|
|
58
70
|
]
|
|
59
71
|
|
|
60
72
|
[tool.pytest.ini_options]
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Colour is decoration, never information. Every role degrades to an attribute
|
|
6
6
|
(or to plain text) when the terminal has no colour, so the installed markers
|
|
7
|
-
(``✔``/``🔒``), the ``S``/``U`` flags and the
|
|
7
|
+
(``✔``/``🔒``), the ``S``/``U`` flags and the ``🌟`` prompts always remain
|
|
8
8
|
readable on a monochrome terminal.
|
|
9
9
|
|
|
10
10
|
The role ids are ``console_window.Theme``'s semantic pairs rather than literal
|
|
@@ -13,22 +13,23 @@ them whenever a theme is applied, so changing the active theme recolours the
|
|
|
13
13
|
whole UI without an edit to any draw code.
|
|
14
14
|
|
|
15
15
|
The role-to-attribute mapping, the process-wide switch and the forgiving lookup
|
|
16
|
-
live in :mod:`console_window
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
of the UI
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
``
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
live in :mod:`console_window` (the ``Appearance`` class, whose module is private
|
|
17
|
+
from 3.0 on), and are re-exported here. Keeping one switch matters: the
|
|
18
|
+
library's theme picker paints its swatches through that layer, so a
|
|
19
|
+
vappman-local copy would leave the picker in colour while the rest of the UI
|
|
20
|
+
went mono.
|
|
21
|
+
|
|
22
|
+
``mono`` is a real console-window theme in 3.0 (it installs the terminal-default
|
|
23
|
+
background and uses the monochrome role attributes), so vappman no longer owns a
|
|
24
|
+
pseudo-theme of its own. What stays here is the preference *pair* the picker
|
|
25
|
+
persists: a palette name plus the colour switch, which can be off for a named
|
|
26
|
+
palette too (``--no-color``, ``NO_COLOR``).
|
|
25
27
|
"""
|
|
26
28
|
|
|
27
29
|
import os
|
|
28
30
|
|
|
29
|
-
from console_window import
|
|
30
|
-
|
|
31
|
-
MONO_ROLES, colors_enabled, semantic_attr, set_colors_enabled,
|
|
31
|
+
from console_window import (
|
|
32
|
+
MONO_ROLES, Theme, colors_enabled, semantic_attr, set_colors_enabled,
|
|
32
33
|
)
|
|
33
34
|
|
|
34
35
|
# Explicit re-exports: the appearance API is read from this module by vappman.
|
|
@@ -38,17 +39,23 @@ __all__ = [
|
|
|
38
39
|
'set_colors_enabled',
|
|
39
40
|
]
|
|
40
41
|
|
|
41
|
-
# vappman's
|
|
42
|
-
|
|
42
|
+
# vappman's no-colour appearance. Since console-window 3.0 this is also a
|
|
43
|
+
# library theme name, which is deliberate: the picker's rotation, the window's
|
|
44
|
+
# recorded theme and a remembered preference all agree on one spelling.
|
|
45
|
+
MONO_THEME = Theme.MONO
|
|
43
46
|
|
|
44
47
|
|
|
45
48
|
def appearance_choices():
|
|
46
49
|
"""Every appearance the picker cycles, in order.
|
|
47
50
|
|
|
48
|
-
console-window's themes come first (in ``Theme.THEMES`` order),
|
|
49
|
-
vappman's no-colour
|
|
51
|
+
console-window's themes come first (in ``Theme.THEMES`` order), with
|
|
52
|
+
vappman's no-colour appearance last, so one cycle always reaches ``mono``.
|
|
53
|
+
``mono`` is a library theme in 3.0 and is already last in the registry; it
|
|
54
|
+
is moved by name rather than trusted to stay there, so a registry reorder
|
|
55
|
+
cannot put it twice or in the middle.
|
|
50
56
|
"""
|
|
51
|
-
|
|
57
|
+
others = [name for name in Theme.list_all() if name != MONO_THEME]
|
|
58
|
+
return others + [MONO_THEME]
|
|
52
59
|
|
|
53
60
|
|
|
54
61
|
def appearance_label(theme, colors):
|
|
@@ -70,8 +77,9 @@ def resolve_ui_prefs(cli_theme=None, no_color=False, saved_theme='',
|
|
|
70
77
|
console-window's ``default``.
|
|
71
78
|
|
|
72
79
|
``theme`` is ``''`` for the library default. The special value ``mono``
|
|
73
|
-
forces ``colors`` off and is reported as ``theme=''``:
|
|
74
|
-
|
|
80
|
+
forces ``colors`` off and is reported as ``theme=''``: vappman remembers the
|
|
81
|
+
pair (palette, colour switch), and ``''`` + ``colors=False`` is the
|
|
82
|
+
canonical spelling of "no palette, no colour".
|
|
75
83
|
"""
|
|
76
84
|
env = os.environ if environ is None else environ
|
|
77
85
|
if cli_theme:
|
|
@@ -7,6 +7,17 @@ import subprocess
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import List, Dict, Tuple, Optional
|
|
9
9
|
|
|
10
|
+
from console_window import Glyphs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def warn_mark():
|
|
14
|
+
"""The two-cell 'pay attention' badge for console output.
|
|
15
|
+
|
|
16
|
+
Asked of console-window's mark registry rather than pasted as a literal, so
|
|
17
|
+
a terminal that cannot draw the emoji gets the registered ASCII fallback.
|
|
18
|
+
"""
|
|
19
|
+
return Glyphs.mark('star_glow')
|
|
20
|
+
|
|
10
21
|
|
|
11
22
|
class AppImageIssue:
|
|
12
23
|
"""Represents a system compatibility issue for AppImages"""
|
|
@@ -243,7 +254,7 @@ def format_report(issues: List[AppImageIssue], distro_id: str, distro_name: str,
|
|
|
243
254
|
infos = [i for i in issues if i.severity == 'info']
|
|
244
255
|
|
|
245
256
|
if critical:
|
|
246
|
-
lines.append("\n
|
|
257
|
+
lines.append(f"\n{warn_mark()} CRITICAL ISSUES (AppImages will likely not work):")
|
|
247
258
|
for issue in critical:
|
|
248
259
|
lines.append(f"\n • {issue.description}")
|
|
249
260
|
if verbose:
|
|
@@ -251,7 +262,7 @@ def format_report(issues: List[AppImageIssue], distro_id: str, distro_name: str,
|
|
|
251
262
|
lines.append(f" Fix: {fix}")
|
|
252
263
|
|
|
253
264
|
if warnings:
|
|
254
|
-
lines.append("\n
|
|
265
|
+
lines.append(f"\n{warn_mark()} WARNINGS (Some AppImages may fail):")
|
|
255
266
|
for issue in warnings:
|
|
256
267
|
lines.append(f"\n • {issue.description}")
|
|
257
268
|
if verbose:
|
|
@@ -298,7 +309,7 @@ def quick_check() -> Tuple[bool, List[str]]:
|
|
|
298
309
|
status_lines.append(f" ✗ {check_name}: {issue.description}")
|
|
299
310
|
status_lines.append(f" Fix: {fix_cmd}")
|
|
300
311
|
elif issue.severity == 'warning':
|
|
301
|
-
status_lines.append(f"
|
|
312
|
+
status_lines.append(f" {warn_mark()} {check_name}: {issue.description}")
|
|
302
313
|
else:
|
|
303
314
|
status_lines.append(f" ℹ {check_name}: {issue.description}")
|
|
304
315
|
else:
|
|
@@ -333,7 +344,7 @@ def main():
|
|
|
333
344
|
print(f" Fix: {fix}")
|
|
334
345
|
elif issue.severity == 'warning':
|
|
335
346
|
fix = issue.fix_commands.get(distro_id) or issue.fix_commands.get('default', 'No fix available')
|
|
336
|
-
print(f"\n
|
|
347
|
+
print(f"\n{warn_mark()} {issue.name}:")
|
|
337
348
|
print(f" {issue.description}")
|
|
338
349
|
print(f" Suggestion: {fix}")
|
|
339
350
|
else:
|
|
@@ -13,6 +13,18 @@ import sys
|
|
|
13
13
|
import shutil
|
|
14
14
|
import subprocess
|
|
15
15
|
|
|
16
|
+
from console_window import Glyphs
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def warn_mark():
|
|
20
|
+
"""The two-cell 'pay attention' badge for console messages.
|
|
21
|
+
|
|
22
|
+
Asked of console-window's mark registry rather than pasted as a literal, so
|
|
23
|
+
a terminal that cannot draw the emoji gets the registered ASCII fallback.
|
|
24
|
+
"""
|
|
25
|
+
return Glyphs.mark('star_glow')
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
class Prerequisites:
|
|
17
29
|
""" Detect / install prereqs """
|
|
18
30
|
def __init__(self):
|
|
@@ -69,7 +81,7 @@ class Prerequisites:
|
|
|
69
81
|
packages = sorted(missing)
|
|
70
82
|
|
|
71
83
|
if not skip_prompt:
|
|
72
|
-
print(f'\n
|
|
84
|
+
print(f'\n{warn_mark()} Missing dependencies: {", ".join(packages)}')
|
|
73
85
|
print(f'\nDetected package manager: {pm_name}')
|
|
74
86
|
|
|
75
87
|
response = input('\nInstall missing dependencies? [y/N]: ').strip().lower()
|
|
@@ -107,7 +119,7 @@ class Prerequisites:
|
|
|
107
119
|
Returns:
|
|
108
120
|
bool: True if installation succeeded or user declined, False on error
|
|
109
121
|
"""
|
|
110
|
-
print('\n
|
|
122
|
+
print(f'\n{warn_mark()} AM/appman is not installed.')
|
|
111
123
|
print('\nAM is a powerful AppImage package manager that allows you to:')
|
|
112
124
|
print(' • Install and manage 2500+ AppImages, Soarpkgs, and AppBundles')
|
|
113
125
|
print(' • Update apps with a single command')
|
|
@@ -183,7 +195,7 @@ class Prerequisites:
|
|
|
183
195
|
has_sudo = shutil.which('sudo') is not None
|
|
184
196
|
has_doas = shutil.which('doas') is not None
|
|
185
197
|
if not has_sudo and not has_doas:
|
|
186
|
-
print('\n
|
|
198
|
+
print(f'\n{warn_mark()} WARNING: Neither "sudo" nor "doas" found.')
|
|
187
199
|
print(' System-level operations may not work correctly.')
|
|
188
200
|
|
|
189
201
|
# Check core dependencies
|
|
@@ -224,7 +236,7 @@ class Prerequisites:
|
|
|
224
236
|
for prog in missing_optional:
|
|
225
237
|
packages_needed.add(optional_deps[prog])
|
|
226
238
|
|
|
227
|
-
print(f'\n
|
|
239
|
+
print(f'\n{warn_mark()} Optional dependencies missing: {", ".join(sorted(missing_optional))}')
|
|
228
240
|
print(' Some features may not work without these packages.')
|
|
229
241
|
|
|
230
242
|
response = input('\nInstall optional dependencies? [y/N]: ').strip().lower()
|
|
@@ -248,11 +260,12 @@ class Prerequisites:
|
|
|
248
260
|
|
|
249
261
|
# After successful installation, exit so user can restart vappman
|
|
250
262
|
print('\n✅ Installation complete!')
|
|
251
|
-
print('\n
|
|
252
|
-
print('\n
|
|
263
|
+
print(f'\n{warn_mark()} Run "appman -h" or "am -h" and answer setup questions.')
|
|
264
|
+
print(f'\n{warn_mark()} ONLY after init, restart vappman to begin using it.')
|
|
253
265
|
sys.exit(0)
|
|
254
266
|
|
|
255
267
|
if optional_install_failed:
|
|
256
|
-
print('
|
|
268
|
+
print(f'{warn_mark()} Core prerequisites satisfied '
|
|
269
|
+
'(optional dependencies had issues).')
|
|
257
270
|
else:
|
|
258
271
|
print('✅ All prerequisites satisfied.')
|
|
@@ -26,7 +26,7 @@ import curses as cs
|
|
|
26
26
|
from console_window import (
|
|
27
27
|
ConsoleWindow, OptionSpinner, ConsoleWindowOpts,
|
|
28
28
|
Screen, ScreenStack, BasicHelpScreen, Context,
|
|
29
|
-
IncrementalSearchBar, InlineConfirmation, Theme
|
|
29
|
+
Glyphs, IncrementalSearchBar, InlineConfirmation, Theme
|
|
30
30
|
)
|
|
31
31
|
from . import __version__
|
|
32
32
|
from .PersistentState import PersistentState
|
|
@@ -56,6 +56,17 @@ INSTALL_OPTIONS = (
|
|
|
56
56
|
INSTALL_OPTION_KEYS = {ord(key): flags for key, _, flags in INSTALL_OPTIONS}
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def notice_mark():
|
|
60
|
+
"""The two-cell 'pay attention' badge used on prompted rows.
|
|
61
|
+
|
|
62
|
+
Asked of console-window's mark registry rather than pasted as a literal, so
|
|
63
|
+
ASCII mode substitutes the registered fallback (``'S '``) and the prompt
|
|
64
|
+
column keeps its two cells. Called at draw time because the glyph mode is
|
|
65
|
+
process state, decided when the first window is created.
|
|
66
|
+
"""
|
|
67
|
+
return Glyphs.mark('star_glow')
|
|
68
|
+
|
|
69
|
+
|
|
59
70
|
def _install_option_key(install_opts):
|
|
60
71
|
"""Return the menu key matching a persisted `install_opts` value."""
|
|
61
72
|
flags = frozenset(x for x in (install_opts or '').split(',') if x)
|
|
@@ -140,18 +151,16 @@ class HomeScreen(VappmanScreen):
|
|
|
140
151
|
rv += ' ╰── ' if corner else ' │ '
|
|
141
152
|
return rv
|
|
142
153
|
|
|
143
|
-
win = self.win
|
|
144
|
-
|
|
145
154
|
# Skip first line (already shown on main line), add up to 'max' continuation lines
|
|
146
155
|
# Continuation lines are already sized correctly from textwrap's subsequent_indent
|
|
147
156
|
indent = make_indent(2+fold_offset, False)
|
|
148
157
|
lines = wrapped_lines[1:1+num]
|
|
149
158
|
for line in lines[:-1]:
|
|
150
159
|
# Indent to fold_offset position
|
|
151
|
-
|
|
160
|
+
self.add_body(indent + line, context=Context("TRANSIENT"))
|
|
152
161
|
indent = make_indent(2+fold_offset, True)
|
|
153
162
|
if len(lines) > 0:
|
|
154
|
-
|
|
163
|
+
self.add_body(indent + wrapped_lines[-1], context=Context("TRANSIENT"))
|
|
155
164
|
|
|
156
165
|
|
|
157
166
|
def get_folded_synopsis(self, offset1, offset2, text):
|
|
@@ -189,14 +198,14 @@ class HomeScreen(VappmanScreen):
|
|
|
189
198
|
pending = app.pending_action
|
|
190
199
|
if label is None or pending is None:
|
|
191
200
|
return ''
|
|
192
|
-
return f'
|
|
201
|
+
return f' {notice_mark()} {label} {pending[1].appname}? Press y or ESC'
|
|
193
202
|
|
|
194
203
|
|
|
195
204
|
def draw_screen(self):
|
|
196
205
|
"""Draw the home screen with app list ⮜–⮞
|
|
197
206
|
"""
|
|
198
207
|
app = self.app
|
|
199
|
-
win = self.win
|
|
208
|
+
win = self.win # window-only services: cols, demo mode, picked context
|
|
200
209
|
all_dbs = bool(app.opts.database == 'ALL')
|
|
201
210
|
# While the incremental search bar is active, suppress the selected-line
|
|
202
211
|
# highlight so the pattern being typed is visually unambiguous.
|
|
@@ -209,7 +218,10 @@ class HomeScreen(VappmanScreen):
|
|
|
209
218
|
"""True when the given row should be shown as the selected line."""
|
|
210
219
|
return not searching and row_idx == win.pick_pos
|
|
211
220
|
|
|
212
|
-
|
|
221
|
+
# Content and the pick state live on the screen (console-window 3.0), so
|
|
222
|
+
# the writers below are Screen methods; only the demo indicator and the
|
|
223
|
+
# terminal width are the window's.
|
|
224
|
+
self.set_pick_mode(not searching)
|
|
213
225
|
win.set_demo_mode(app.opts.demo_mode)
|
|
214
226
|
|
|
215
227
|
title = "APPMAN"
|
|
@@ -252,7 +264,7 @@ class HomeScreen(VappmanScreen):
|
|
|
252
264
|
# Colour is decoration: installed rows are also marked by ✔/🔒
|
|
253
265
|
# and the S/U flags, so HOTSWAP only reinforces them.
|
|
254
266
|
attr = semantic_attr(Theme.HOTSWAP) if status == 'installed' else None
|
|
255
|
-
|
|
267
|
+
self.add_body(line, attr=attr, context=Context(status, info=ns))
|
|
256
268
|
|
|
257
269
|
if picked(idx):
|
|
258
270
|
selected_key = (appname, db)
|
|
@@ -267,7 +279,7 @@ class HomeScreen(VappmanScreen):
|
|
|
267
279
|
# TRANSIENT (non-pickable) so the engine's auto-abut
|
|
268
280
|
# reserves the row even when the pick sits at the
|
|
269
281
|
# viewport bottom.
|
|
270
|
-
|
|
282
|
+
self.add_body(
|
|
271
283
|
prompt, attr=cs.A_BOLD | semantic_attr(Theme.DANGER),
|
|
272
284
|
context=Context("TRANSIENT", pickable=False))
|
|
273
285
|
idx += 1
|
|
@@ -293,7 +305,7 @@ class HomeScreen(VappmanScreen):
|
|
|
293
305
|
else:
|
|
294
306
|
line += ns.synopsis
|
|
295
307
|
|
|
296
|
-
|
|
308
|
+
self.add_body(line, context=Context("uninstalled", info=ns))
|
|
297
309
|
|
|
298
310
|
if picked(idx):
|
|
299
311
|
selected_key = (appname, db)
|
|
@@ -304,11 +316,11 @@ class HomeScreen(VappmanScreen):
|
|
|
304
316
|
# TRANSIENT (non-pickable) so the engine's auto-abut
|
|
305
317
|
# reserves both lines -- a DECOR row would not be
|
|
306
318
|
# counted, and the prompt could fall off-screen.
|
|
307
|
-
|
|
308
|
-
f'
|
|
319
|
+
self.add_body(
|
|
320
|
+
f' {notice_mark()} Install {appname}?',
|
|
309
321
|
attr=cs.A_BOLD | semantic_attr(Theme.DANGER),
|
|
310
322
|
context=Context("TRANSIENT", pickable=False))
|
|
311
|
-
|
|
323
|
+
self.add_body(
|
|
312
324
|
' ' + install_options_legend(app.opts.install_opts),
|
|
313
325
|
attr=cs.A_DIM,
|
|
314
326
|
context=Context("TRANSIENT", pickable=False))
|
|
@@ -324,7 +336,7 @@ class HomeScreen(VappmanScreen):
|
|
|
324
336
|
#############################################
|
|
325
337
|
header1 = f'{title} {app.get_keys_line()}'
|
|
326
338
|
# Use fancy header formatting to highlight keys automatically
|
|
327
|
-
|
|
339
|
+
self.add_fancy_header(header1, app.opts.fancy_header, line_attr=cs.A_DIM)
|
|
328
340
|
|
|
329
341
|
# Build dynamic action keys (e.g., " [r]mv [u]pd [b]kup")
|
|
330
342
|
# Get base header line and combine with dynamic actions
|
|
@@ -349,7 +361,7 @@ class HomeScreen(VappmanScreen):
|
|
|
349
361
|
else:
|
|
350
362
|
header2 += ' [i]nstall [a]bout'
|
|
351
363
|
|
|
352
|
-
|
|
364
|
+
self.add_fancy_header(header2, app.opts.fancy_header, line_attr=cs.A_DIM)
|
|
353
365
|
|
|
354
366
|
def appman_on_installed(self, verb):
|
|
355
367
|
""" TBD """
|
|
@@ -610,13 +622,13 @@ class Vappman(Prerequisites):
|
|
|
610
622
|
win_opts.dialog_abort = True
|
|
611
623
|
win_opts.ctrl_c_terminates = False
|
|
612
624
|
win_opts.min_cols_rows = (60, 10)
|
|
613
|
-
|
|
625
|
+
# 'mono' is a real theme in console-window 3.0, so the window is created
|
|
626
|
+
# on the appearance actually in effect instead of being relabelled after
|
|
627
|
+
# construction (the switch alone used to carry the choice).
|
|
628
|
+
win_opts.theme = MONO_THEME if not self.appearance_colors else (
|
|
629
|
+
self.appearance_theme or 'default')
|
|
614
630
|
|
|
615
631
|
self.win = ConsoleWindow(win_opts)
|
|
616
|
-
if not self.appearance_colors:
|
|
617
|
-
# 'mono' is not a library theme: the installed palette stays at the
|
|
618
|
-
# default, but the window names the appearance actually in effect.
|
|
619
|
-
self.win.theme = MONO_THEME
|
|
620
632
|
|
|
621
633
|
# Initialize screens and screen stack
|
|
622
634
|
self.screens = {
|
|
@@ -626,10 +638,13 @@ class Vappman(Prerequisites):
|
|
|
626
638
|
}
|
|
627
639
|
self.ss = ScreenStack(self.win, None, SCREENS, self.screens)
|
|
628
640
|
|
|
629
|
-
|
|
641
|
+
# console-window 3.0 registers ``quit`` (q, ^C) and ``escape``
|
|
642
|
+
# (ESC, ^J, ENTER) itself, before anything else, so the quit key set is
|
|
643
|
+
# a constructor argument rather than a second registration of the same
|
|
644
|
+
# name. 'x' stays an alternative quit key; ^C is delivered as a key
|
|
645
|
+
# because ctrl_c_terminates is False above.
|
|
646
|
+
spin = self.spin = OptionSpinner(stack=self.ss, quit_keys='qx\x03')
|
|
630
647
|
self.opts = spin.default_obj
|
|
631
|
-
spin.add_key('quit', 'q,x - quit program (CTL-C disabled)',
|
|
632
|
-
genre='action', keys='qx')
|
|
633
648
|
spin.add_key('help', '? - enter help screen', genre='action')
|
|
634
649
|
dbs = sorted(list(self.cache_mgr.dbs))
|
|
635
650
|
spin.add_key('database', 'd - select app database', vals=['ALL'] + dbs)
|
|
@@ -649,11 +664,17 @@ class Vappman(Prerequisites):
|
|
|
649
664
|
spin.add_key('update_all', 'U - update ALL installed apps', genre='action')
|
|
650
665
|
spin.add_key('reinstall_all', 'R - reinstall ALL apps w updated install script', genre='action')
|
|
651
666
|
spin.add_key('slash', '/ - filter apps by keywords or regex', genre='action')
|
|
652
|
-
|
|
667
|
+
# ESC is reserved by the built-in 'escape' action; claiming it on HOME
|
|
668
|
+
# only is an explicit, per-screen override, which is what clears a filter
|
|
669
|
+
# without leaving the screen.
|
|
670
|
+
spin.add_key('escape_filter', 'ESC - clear filter and jump to top',
|
|
671
|
+
genre='action', keys=27, scope=HOME_ST)
|
|
653
672
|
|
|
654
673
|
spin.add_key('install', 'i - install uninstalled app', genre='action')
|
|
674
|
+
# ENTER/^J also belong to the built-in 'escape' action, so the
|
|
675
|
+
# install/uninstall shortcut is an explicit HOME-only override.
|
|
655
676
|
spin.add_key('default', 'ENTER - install/uninstall app',
|
|
656
|
-
genre='action', keys=[cs.KEY_ENTER, 10])
|
|
677
|
+
genre='action', keys=[cs.KEY_ENTER, 10], scope=HOME_ST)
|
|
657
678
|
spin.add_key('remove', 'r - remove installed app', genre='action')
|
|
658
679
|
|
|
659
680
|
spin.add_key('sandbox', 'S - Sandbox/Unsandbox app', genre='action')
|
|
@@ -763,7 +784,10 @@ class Vappman(Prerequisites):
|
|
|
763
784
|
result = subprocess.run(command, stdout=subprocess.PIPE,
|
|
764
785
|
stderr=subprocess.PIPE, check=False)
|
|
765
786
|
except Exception as exc:
|
|
766
|
-
|
|
787
|
+
# stop_all() is the permanent teardown in every mode; the bare
|
|
788
|
+
# ConsoleWindow.stop_curses() class call only exists for
|
|
789
|
+
# singleton mode and raises when no window was ever created.
|
|
790
|
+
ConsoleWindow.stop_all()
|
|
767
791
|
if switch_to_system_mode is True:
|
|
768
792
|
self.appman.set_system_mode_cheat(False)
|
|
769
793
|
print(f'FAILED: {command}: {exc}')
|
|
@@ -831,9 +855,13 @@ class Vappman(Prerequisites):
|
|
|
831
855
|
last_database = self.opts.database
|
|
832
856
|
|
|
833
857
|
while True:
|
|
834
|
-
#
|
|
835
|
-
|
|
836
|
-
|
|
858
|
+
# The 3.0 loop: clear() adopts the screen on the window (and resets
|
|
859
|
+
# its content), draw_screen() appends the rows, render() paints them.
|
|
860
|
+
# Clearing first is what makes the screen current, so the writers in
|
|
861
|
+
# draw_screen() and the pick state read below belong to it.
|
|
862
|
+
screen = self.screens[self.ss.curr.num]
|
|
863
|
+
screen.clear()
|
|
864
|
+
screen.draw_screen()
|
|
837
865
|
|
|
838
866
|
win.render()
|
|
839
867
|
key = win.prompt(seconds=self.next_prompt_seconds[0])
|
|
@@ -882,8 +910,6 @@ class Vappman(Prerequisites):
|
|
|
882
910
|
last_database = self.opts.database
|
|
883
911
|
self.restore_selection(self.pick_identity)
|
|
884
912
|
|
|
885
|
-
win.clear()
|
|
886
|
-
|
|
887
913
|
def _handle_confirmation_key(self, key):
|
|
888
914
|
"""Route one key to the active inline install/remove confirmation."""
|
|
889
915
|
if self.confirmation.action_type == 'install':
|
|
@@ -1056,20 +1082,20 @@ class Vappman(Prerequisites):
|
|
|
1056
1082
|
"""Make *name* live ('mono' turns colour off) and remember it.
|
|
1057
1083
|
|
|
1058
1084
|
The preference is written immediately so an early exit cannot lose it,
|
|
1059
|
-
and the window is told which
|
|
1060
|
-
|
|
1061
|
-
|
|
1085
|
+
and the window is told which theme was installed. ``mono`` is a real
|
|
1086
|
+
console-window theme in 3.0, so the window is given that name: a reader
|
|
1087
|
+
of ``win.theme`` sees the no-colour appearance, and ``Theme.set_active``
|
|
1088
|
+
(called by ``set_theme``) turns the semantic-colour switch off with it.
|
|
1089
|
+
The switch is also set here, because vappman resolves the preference
|
|
1090
|
+
pair before any window exists and a host without ``set_theme`` must
|
|
1091
|
+
still end up monochrome.
|
|
1062
1092
|
"""
|
|
1063
1093
|
mono = name == MONO_THEME
|
|
1064
1094
|
self.appearance_colors = not mono
|
|
1065
1095
|
# '' is the library default; keep the saved file canonical.
|
|
1066
1096
|
self.appearance_theme = '' if mono or name == 'default' else name
|
|
1097
|
+
self.win.set_theme(MONO_THEME if mono else name)
|
|
1067
1098
|
set_colors_enabled(not mono)
|
|
1068
|
-
self.win.set_theme('default' if mono else name)
|
|
1069
|
-
if mono:
|
|
1070
|
-
# 'mono' is not a library theme: the installed palette stays at the
|
|
1071
|
-
# library default, but the window names the appearance in effect.
|
|
1072
|
-
self.win.theme = MONO_THEME
|
|
1073
1099
|
self.disk_state.theme = self.appearance_theme
|
|
1074
1100
|
self.disk_state.colors = self.appearance_colors
|
|
1075
1101
|
self.disk_state.save()
|
|
@@ -1210,7 +1236,8 @@ def main():
|
|
|
1210
1236
|
print(line)
|
|
1211
1237
|
|
|
1212
1238
|
if has_critical:
|
|
1213
|
-
print("\n
|
|
1239
|
+
print(f"\n{notice_mark()} Critical issues found! "
|
|
1240
|
+
"Run 'vappman --check-appimage' for detailed fixes")
|
|
1214
1241
|
print("Starting TUI in 3 seconds...")
|
|
1215
1242
|
time.sleep(3)
|
|
1216
1243
|
print()
|
|
@@ -1221,7 +1248,9 @@ def main():
|
|
|
1221
1248
|
except KeyboardInterrupt:
|
|
1222
1249
|
pass
|
|
1223
1250
|
except Exception as exce:
|
|
1224
|
-
|
|
1251
|
+
# Tear down whatever exists (a failure before the window was created is
|
|
1252
|
+
# common here, and stop_all() tolerates it).
|
|
1253
|
+
ConsoleWindow.stop_all()
|
|
1225
1254
|
print("exception:", str(exce))
|
|
1226
1255
|
print(traceback.format_exc())
|
|
1227
1256
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|