vappman 0.7__py3-none-any.whl → 0.8__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.
vappman/PowerWindow.py CHANGED
@@ -588,7 +588,7 @@ class Window:
588
588
  def alert(self, title='ALERT', message='', height=1, width=80):
589
589
  """Alert box"""
590
590
  def mod_key(key):
591
- return 7 if key == 10 else key
591
+ return 7 if key in (10, curses.KEY_ENTER) else key
592
592
 
593
593
  # need 3 extra cols for rectangle (so we don't draw in southeast corner)
594
594
  # and 3 rows (top/prompt/bottom)
vappman/main.py CHANGED
@@ -6,7 +6,7 @@ Interactive, visual thin layer atop appman
6
6
  # pylint: disable=broad-exception-caught,consider-using-with
7
7
  # pylint: disable=too-many-instance-attributes,too-many-branches
8
8
  # pylint: disable=too-many-return-statements,too-many-statements
9
- # pylint: disable=consider-using-in
9
+ # pylint: disable=consider-using-in,too-many-nested-blocks
10
10
  # pylint: disable=wrong-import-position,disable=wrong-import-order
11
11
  # import VirtEnv
12
12
  # VirtEnv.ensure_venv(__name__)
@@ -14,6 +14,7 @@ Interactive, visual thin layer atop appman
14
14
  import os
15
15
  import sys
16
16
  import re
17
+ import glob
17
18
  import shutil
18
19
  import subprocess
19
20
  import traceback
@@ -34,7 +35,7 @@ class Vappman:
34
35
  spin.add_key('help_mode', '? - toggle help screen', vals=[False, True])
35
36
 
36
37
  # EXPAND
37
- other = 'airbou/qscU'
38
+ other = 'airtbou/qxscU'
38
39
  other_keys = set(ord(x) for x in other)
39
40
  other_keys.add(cs.KEY_ENTER)
40
41
  other_keys.add(27) # ESCAPE
@@ -49,6 +50,9 @@ class Vappman:
49
50
  self.check_preqreqs()
50
51
  self.apps = self.cmd_dict('appman list')
51
52
  self.installs = self.get_installed() # dict keyed by app
53
+ self.appman_dir = self.get_appman_dir()
54
+ self.dot_desktop_dir = self.get_dot_desktop_dir()
55
+ self.terminal_emulator = None
52
56
  self.win = Window(head_line=True, body_rows=len(self.apps)+20, head_rows=10,
53
57
  keys=spin.keys ^ other_keys, mod_pick=self.mod_pick)
54
58
 
@@ -94,6 +98,43 @@ class Vappman:
94
98
  rv = self.cmd_dict('appman files --byname')
95
99
  return rv
96
100
 
101
+ @staticmethod
102
+ def get_appman_dir():
103
+ """ Try to figure out where the apps are stored. """
104
+
105
+ appman_dir = None, None
106
+ try:
107
+ config_dir = os.getenv('XDG_CONFIG_HOME')
108
+ if not config_dir:
109
+ config_dir = os.path.join(os.getenv('HOME'), '.config')
110
+ config_file = os.path.join(config_dir, 'appman', 'appman-config')
111
+ with open(config_file, 'r', encoding='utf-8') as fh:
112
+ appman_dir = fh.read().strip()
113
+ appman_dir = os.path.join(os.getenv('HOME'), appman_dir)
114
+ os.listdir(appman_dir)
115
+ return appman_dir
116
+ except Exception as exc:
117
+ print(f'NOTE: cannot get appman dir; tried below {appman_dir!r}; {exc}')
118
+ print(' Check if contents of ~/config/appman/appman-config'
119
+ + ' is the subdir of $HOME w your appman apps')
120
+ return None
121
+
122
+ @staticmethod
123
+ def get_dot_desktop_dir():
124
+ """ Try to figure out where the .desktop files are stored. """
125
+
126
+ try:
127
+ data_dir = os.getenv('XDG_DATA_HOME')
128
+ if not data_dir:
129
+ data_dir = os.path.join(os.getenv('HOME'), '.local', 'share')
130
+ dot_dir = os.path.join(data_dir, 'applications')
131
+ os.listdir(dot_dir)
132
+ return dot_dir
133
+ except Exception as exc:
134
+ print(f'NOTE: cannot get .desktop dir; tried below {data_dir!r}; {exc}')
135
+ print(' Check if contents of ~/.local/share/applications for .desktop files')
136
+ return None
137
+
97
138
  def main_loop(self):
98
139
  """ TBD """
99
140
 
@@ -106,7 +147,7 @@ class Vappman:
106
147
  # EXPAND
107
148
  lines = [
108
149
  'ALWAYS AVAILABLE:',
109
- ' q - quit program (CTL-C disabled)',
150
+ ' q or x - quit program (CTL-C disabled)',
110
151
  ' a - about (more info about app)',
111
152
  ' s - sync (update appman itself)',
112
153
  ' c - clean (remove unneeded files/folters)',
@@ -119,6 +160,7 @@ class Vappman:
119
160
  ' r - remove installed app',
120
161
  ' b - backup installed app',
121
162
  ' u - update installed app',
163
+ ' t - test by opening a terminal emulator and launching the app'
122
164
  ' o - overwrite app from its backup',
123
165
 
124
166
  ]
@@ -127,6 +169,12 @@ class Vappman:
127
169
  else:
128
170
  def wanted(line):
129
171
  return not self.filter or self.filter.search(line)
172
+ def version_of(app):
173
+ # ◆ krita | 5.2.2 | appimage-type2 | 355 MiB
174
+ fields = self.installs[app].split('|')
175
+ if len(fields) >= 2:
176
+ return fields[1].strip()
177
+ return '?version?'
130
178
 
131
179
  # self.win.set_pick_mode(self.opts.pick_mode, self.opts.pick_size)
132
180
  self.win.set_pick_mode(True)
@@ -135,7 +183,7 @@ class Vappman:
135
183
  if app in self.apps:
136
184
  line = self.apps[app]
137
185
  if wanted(line[2:]):
138
- line = '✔✔✔' + line[1:]
186
+ line = f'✔✔✔ {app} [{version_of(app)}] :{line.split(':', maxsplit=1)[1]}'
139
187
  self.win.add_body(line)
140
188
  for app, line in self.apps.items():
141
189
  if app not in self.installs and wanted(line[2:]):
@@ -174,6 +222,7 @@ class Vappman:
174
222
  actions['b'] = 'bkup'
175
223
  actions['o'] = 'overwr'
176
224
  actions['u'] = 'upd'
225
+ actions['t'] = 'test'
177
226
  else:
178
227
  actions['i'] = 'install'
179
228
 
@@ -213,6 +262,95 @@ class Vappman:
213
262
  self.installs = self.get_installed()
214
263
  Window._start_curses()
215
264
 
265
+ @staticmethod
266
+ def launch_desktop_file(desktop_file_path):
267
+ """ Launch the .desktop file using xdg-open in a detached process """
268
+ try:
269
+ trial = ['xdg-open', desktop_file_path]
270
+ subprocess.Popen(trial,
271
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True)
272
+ return None
273
+ except Exception:
274
+ return trial
275
+
276
+ def launch_in_terminal(self, executable):
277
+ """ Find a terminal emulator"""
278
+ if not self.terminal_emulator:
279
+ maybes = [
280
+ [ 'konsole', '--noclose', '-e', '"{command}"'],
281
+ [ 'gnome-terminal', '--', 'bash', '-c', '"{command}"; exec bash' ],
282
+ [ 'xfce4-terminal', '--hold', '--command="{command}"' ],
283
+ [ 'lxterminal', '-e', """bash -c '"{command}"; echo; read -p "Press Enter to close..."'"""],
284
+ # [ terminator', ],
285
+ # [ alacritty', ],
286
+ # [ termite', ],
287
+ # [ urxvt', ],
288
+ # [ sakura', ],
289
+ # [ tilix', ],
290
+ # [ kitty', ],
291
+ # [ hyper', ],
292
+ # [ guake', ],
293
+ # [ yakuake', ],
294
+ ]
295
+ for maybe in maybes:
296
+ if shutil.which(maybe[0]):
297
+ self.terminal_emulator = maybe
298
+ break
299
+ if self.terminal_emulator:
300
+ try:
301
+ trial = []
302
+ for wd in self.terminal_emulator:
303
+ trial.append(wd.replace('{command}', executable))
304
+ subprocess.Popen(trial)
305
+ return None
306
+ except Exception:
307
+ return trial
308
+ return trial
309
+
310
+ def launch_app(self, app):
311
+ """ Try to run an app."""
312
+ # First dig out where it might be installed as a .desktop file
313
+ # by searching the 'remove' script
314
+ def get_unique_words_from_file(file_path):
315
+ seen_words = set()
316
+ with open(file_path, 'r', encoding='utf-8') as file:
317
+ for line in file:
318
+ line_words = line.split()
319
+ for word in line_words:
320
+ if word not in seen_words:
321
+ seen_words.add(word)
322
+ return seen_words
323
+
324
+ failures = []
325
+ executables = []
326
+ try:
327
+ for globname in get_unique_words_from_file(
328
+ os.path.join(self.appman_dir, app, 'remove')):
329
+ results = glob.glob(globname)
330
+ if '/share/applications/AM-ZZZ' in globname:
331
+ for result in results:
332
+ if result.endswith('.desktop'):
333
+ failure = self.launch_desktop_file(result)
334
+ if failure:
335
+ failures.append(' '.join(failure))
336
+ return
337
+ elif '/.local/bin/' in globname:
338
+ for result in results:
339
+ if os.access(result, os.X_OK):
340
+ executables.append(result)
341
+ for executable in executables:
342
+ failure = self.launch_in_terminal(executable)
343
+ if failure:
344
+ failures.append(' '.join(failure))
345
+ return
346
+ except Exception as exc:
347
+ failures += f'cannot find .desktop/executable to run [{exc}]'
348
+ if failures:
349
+ message = ' '.join([f'Cannot launch {app}'] + failures)
350
+ self.win.alert(message=message)
351
+
352
+ self.launch_app(self.pick_app)
353
+
216
354
  def do_key(self, key):
217
355
  """ TBD """
218
356
  if not key:
@@ -222,21 +360,21 @@ class Vappman:
222
360
  self.opts.help_mode = False
223
361
  return True
224
362
  if self.pick_is_installed:
225
- key = ord('r') # removed installed app
363
+ key = ord('r') # remove installed app
226
364
  else:
227
365
  key = ord('i') # install uninstalled app
228
366
 
229
367
  if key in self.spin.keys:
230
368
  value = self.spin.do_key(key, self.win)
231
369
  return value
232
-
370
+
233
371
  if key == 27: # ESCAPE
234
372
  self.prev_filter = ''
235
373
  self.filter = None
236
374
  self.win.pick_pos = 0
237
375
  return None
238
376
 
239
- if key == ord('q'):
377
+ if key in (ord('q'), ord('x')):
240
378
  self.win.stop_curses()
241
379
  os.system('clear; stty sane')
242
380
  sys.exit(0)
@@ -249,6 +387,10 @@ class Vappman:
249
387
  self.run_appman(f'appman remove {self.pick_app}')
250
388
  return None
251
389
 
390
+ if key == ord('t') and self.pick_is_installed:
391
+ self.launch_app(self.pick_app)
392
+ return None
393
+
252
394
  if key == ord('s'):
253
395
  self.run_appman('appman sync')
254
396
  if key == ord('c'):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vappman
3
- Version: 0.7
3
+ Version: 0.8
4
4
  Summary: A visual wrapper for appman
5
5
  Author-email: Joe Defen <joedef@google.com>
6
6
  License: MIT
@@ -41,7 +41,7 @@ But it does NOT cover:
41
41
  * (-v) version of appman
42
42
  * --force-latest to get the most recent stable release AND
43
43
  all other options and unmentioned commands.
44
-
44
+
45
45
  ## Usage
46
46
  * Run `vappman` from the command line.
47
47
  * It presents some keys available on the top line.
@@ -67,8 +67,9 @@ But it does NOT cover:
67
67
  * `ESC` clears the filter and jumps to the top of the listing.
68
68
  * Each time the filter is changed, the position jumps to the top of the listing.
69
69
  * Use `i` to install apps, and `r` to remove apps. When you install or remove an app, `appman` drops out of `curses` mode, runs the `appman` command so you can see the result, and then prompts your to hit ENTER to return to `vappman.
70
+ * Use `t` to "test" an installed app. This launches a terminal emulator and then the app so you can see issues. This is not for daily use obviously, but for after install or when having unknown issues and you wish to start the investigation.
70
71
 
71
- ## Example Screenshot
72
+ ## Example Screenshot (of v0.7 ... current release will vary slightly)
72
73
  ![vappman-with-filter](https://github.com/joedefen/vappman/blob/main/images/vappman-with-filter.png?raw=true).
73
74
 
74
75
  ---
@@ -84,3 +85,5 @@ NOTES: in this example:
84
85
  * the fixed top line shows some of the available action keys (e.g., `q` quits the app)
85
86
  * use `?` to open the help screen describing all keys (including navigation)
86
87
 
88
+ ## Screen Recording (Intro to vappman based on v0.7)
89
+ [![Screen Recording](https://i9.ytimg.com/vi_webp/NUHYN9_DZtA/mq3.webp?sqp=CMTu4LMG-oaymwEmCMACELQB8quKqQMa8AEB-AHqBYAC4AOKAgwIABABGEogZShRMA8=&rs=AOn4CLBaBrOpAhJkRIQQNNdCzYaqpOYl-Q)](https://youtu.be/NUHYN9_DZtA)
@@ -0,0 +1,9 @@
1
+ vappman/PowerWindow.py,sha256=pQGXsAMeuiHn-vtEiVG0rgW1eCslh3ukC-VrPhH_j3k,28587
2
+ vappman/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ vappman/main.py,sha256=FBTCzbOa9dCyzTHX3ietF9_avrMRareYFxmnd65wc2M,17066
4
+ vappman-0.8.dist-info/LICENSE,sha256=qB9OdnyyF6WYHiEIXVm0rOSdcf8e2ctorrtWs6CC5lU,1062
5
+ vappman-0.8.dist-info/METADATA,sha256=BJMibKDn0HtJFZcvP55MtNLnqK1P77NeTFOHkaDTm7o,4921
6
+ vappman-0.8.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
7
+ vappman-0.8.dist-info/entry_points.txt,sha256=7_1MiUvkCJoElLePOCJYqhkQN4xmadBRQCKupOwzt90,46
8
+ vappman-0.8.dist-info/top_level.txt,sha256=5_Gb5oZh7s2-i62gLXZ6INVALAV9D0-yqh0TvNqpPC4,8
9
+ vappman-0.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.0)
2
+ Generator: setuptools (70.1.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,9 +0,0 @@
1
- vappman/PowerWindow.py,sha256=OLCX-RkbJZ2wwaY7M-4Eo9PQuR95TWrGNIY8DdVRpsE,28567
2
- vappman/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- vappman/main.py,sha256=8I97lvSqpkPzQ51q_NDPb6dL18Th-ZHm_PRTT6BE9Ec,11105
4
- vappman-0.7.dist-info/LICENSE,sha256=qB9OdnyyF6WYHiEIXVm0rOSdcf8e2ctorrtWs6CC5lU,1062
5
- vappman-0.7.dist-info/METADATA,sha256=_xoExbsd16j5PAWgJrvaSw3uiW18aWY8tRfeJ53HLnM,4360
6
- vappman-0.7.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
7
- vappman-0.7.dist-info/entry_points.txt,sha256=7_1MiUvkCJoElLePOCJYqhkQN4xmadBRQCKupOwzt90,46
8
- vappman-0.7.dist-info/top_level.txt,sha256=5_Gb5oZh7s2-i62gLXZ6INVALAV9D0-yqh0TvNqpPC4,8
9
- vappman-0.7.dist-info/RECORD,,
File without changes