tacklebox-cli 0.2.2__tar.gz → 0.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.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tacklebox-cli
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A small collection of CLI utilities.
5
5
  Project-URL: Homepage, https://c.csw.im/cswimr/tacklebox
6
6
  Project-URL: Issues, https://c.csw.im/cswimr/tacklebox/issues
7
- Project-URL: source_archive, https://c.csw.im/cswimr/tacklebox/archive/a338ec567b0358a40fa7ca3bd3504309a5046ba1.tar.gz
7
+ Project-URL: source_archive, https://c.csw.im/cswimr/tacklebox/archive/d808a4b8b4f6e697fae111ad82ffa49b4d44e668.tar.gz
8
8
  Author-email: cswimr <seaswimmerthefsh@gmail.com>
9
9
  License: The MIT License (MIT)
10
10
  =====================
@@ -34,11 +34,10 @@ License: The MIT License (MIT)
34
34
  License-File: LICENSE.md
35
35
  Classifier: License :: OSI Approved :: MIT License
36
36
  Classifier: Programming Language :: Python
37
- Classifier: Programming Language :: Python :: 3.10
38
37
  Classifier: Programming Language :: Python :: 3.11
39
38
  Classifier: Programming Language :: Python :: 3.12
40
39
  Classifier: Programming Language :: Python :: 3.13
41
- Requires-Python: >=3.10
40
+ Requires-Python: >=3.11
42
41
  Requires-Dist: desktop-notifier>=6.1.0
43
42
  Requires-Dist: platformdirs>=4.3.0
44
43
  Requires-Dist: zipline-py[cli]>=0.27.0
@@ -68,13 +67,13 @@ a
68
67
  Uses the [zipline.py](https://pypi.org/project/zipline-py/) library alongside KDE's [Spectacle](https://invent.kde.org/plasma/spectacle) application to take a screenshot or screen recording and automatically upload it to a [Zipline](https://github.com/diced/zipline) instance. This automatically reads Spectacle's configuration files to determine file formats.
69
68
 
70
69
  ```bash
71
- $ tacklebox spectacle \
70
+ $ tacklebox spectacle --mode region \
72
71
  --server "https://zipline.example.com" \
73
72
  --token "$(cat /file/containing/zipline/token)" \
74
73
  | tacklebox copy --trim
75
74
 
76
75
  # or to record a video
77
- $ tacklebox spectacle --record \
76
+ $ tacklebox spectacle --mode region --record \
78
77
  --server "https://zipline.example.com" \
79
78
  --token "$(cat /file/containing/zipline/token)" \
80
79
  | tacklebox copy --trim
@@ -22,13 +22,13 @@ a
22
22
  Uses the [zipline.py](https://pypi.org/project/zipline-py/) library alongside KDE's [Spectacle](https://invent.kde.org/plasma/spectacle) application to take a screenshot or screen recording and automatically upload it to a [Zipline](https://github.com/diced/zipline) instance. This automatically reads Spectacle's configuration files to determine file formats.
23
23
 
24
24
  ```bash
25
- $ tacklebox spectacle \
25
+ $ tacklebox spectacle --mode region \
26
26
  --server "https://zipline.example.com" \
27
27
  --token "$(cat /file/containing/zipline/token)" \
28
28
  | tacklebox copy --trim
29
29
 
30
30
  # or to record a video
31
- $ tacklebox spectacle --record \
31
+ $ tacklebox spectacle --mode region --record \
32
32
  --server "https://zipline.example.com" \
33
33
  --token "$(cat /file/containing/zipline/token)" \
34
34
  | tacklebox copy --trim
@@ -3,12 +3,11 @@ name = "tacklebox-cli"
3
3
  description = "A small collection of CLI utilities."
4
4
  authors = [{ name = "cswimr", email = "seaswimmerthefsh@gmail.com" }]
5
5
  readme = "README.md"
6
- requires-python = ">=3.10"
6
+ requires-python = ">=3.11"
7
7
  license = { file = "LICENSE.md" }
8
8
  classifiers = [
9
9
  "License :: OSI Approved :: MIT License",
10
10
  "Programming Language :: Python",
11
- "Programming Language :: Python :: 3.10",
12
11
  "Programming Language :: Python :: 3.11",
13
12
  "Programming Language :: Python :: 3.12",
14
13
  "Programming Language :: Python :: 3.13",
@@ -21,7 +20,11 @@ dependencies = [
21
20
  dynamic = ["version", "urls"]
22
21
 
23
22
  [dependency-groups]
24
- dev = ["basedpyright==1.29.2", "ruff==0.11.12"]
23
+ dev = [
24
+ "basedpyright==1.29.2",
25
+ "pyinstrument==5.0.2",
26
+ "ruff==0.11.12",
27
+ ]
25
28
 
26
29
  [project.scripts]
27
30
  tacklebox = "tacklebox.entrypoint:app"
@@ -5,7 +5,7 @@ import subprocess
5
5
  import sys
6
6
  import tempfile
7
7
  from datetime import datetime, timezone
8
- from enum import IntEnum
8
+ from enum import IntEnum, StrEnum
9
9
  from pathlib import Path
10
10
  from shutil import which
11
11
  from typing import Annotated
@@ -28,7 +28,7 @@ if not platform.system() == "Linux":
28
28
  app = Typer()
29
29
 
30
30
 
31
- class VideoFormats(IntEnum):
31
+ class VideoFormat(IntEnum):
32
32
  """Because Spectacle is weird and doesn't just use file extensions for storing video formats 😭"""
33
33
 
34
34
  WEBM = 0
@@ -44,22 +44,71 @@ class VideoFormats(IntEnum):
44
44
  return self.name.lower()
45
45
 
46
46
 
47
- def _read_spectacle_config() -> tuple[str, VideoFormats]:
47
+ def _read_spectacle_config() -> tuple[str, VideoFormat]:
48
48
  """Read the Spectacle config file and return the configured file formats."""
49
49
  path = Path(user_config_dir("spectaclerc"))
50
50
  if not path.exists():
51
- return "png", VideoFormats.WEBM
51
+ return "png", VideoFormat.WEBM
52
52
 
53
- config = configparser.ConfigParser()
53
+ config = configparser.ConfigParser(strict=False)
54
54
  config.read(path)
55
55
 
56
56
  preferred_image_format = config.get("ImageSave", "preferredImageFormat", fallback="png").lower()
57
57
 
58
- preferred_video_format = VideoFormats(config.getint("VideoSave", "preferredVideoFormat", fallback=0))
58
+ preferred_video_format = VideoFormat(config.getint("VideoSave", "preferredVideoFormat", fallback=0))
59
59
 
60
60
  return preferred_image_format, preferred_video_format
61
61
 
62
62
 
63
+ class SpectacleMode(StrEnum):
64
+ REGION = "region"
65
+ DESKTOP = "desktop"
66
+ MONITOR = "monitor"
67
+ WINDOW = "window"
68
+ ACTIVEWINDOW = "activewindow"
69
+ TRANSIENT = "transient"
70
+
71
+ @classmethod
72
+ def complete_format(cls, incomplete: str) -> list[tuple[str, str] | str]:
73
+ """Generate autocompletion strings for Typer."""
74
+ completions: list[tuple[str, str] | str] = []
75
+ for mode in SpectacleMode:
76
+ if mode.startswith(incomplete):
77
+ completions.append((mode, mode.completion_string) if mode.completion_string else mode)
78
+ return completions
79
+
80
+ @property
81
+ def completion_string(self) -> str | None:
82
+ argument = self.get_argument()
83
+ record_argument = self.get_argument(True)
84
+
85
+ completion_strings: dict[SpectacleMode, str] = {
86
+ SpectacleMode.REGION: f"Capture a retangular region of the desktop. ({argument} | {record_argument})",
87
+ SpectacleMode.DESKTOP: f"Capture the entire desktop (default). When recording, this will capture only the current monitor. ({argument} | {record_argument})",
88
+ SpectacleMode.MONITOR: f"Capture the current monitor. ({argument} | {record_argument})",
89
+ SpectacleMode.WINDOW: f"Capture the window currently under the cursor, including parents of pop-up menus. ({argument} | {record_argument})",
90
+ SpectacleMode.ACTIVEWINDOW: f"Capture the currently selected window. ({argument} | {record_argument})",
91
+ SpectacleMode.TRANSIENT: f"Capture the window currently under the cursor, excluding parents of pop-up menus. ({argument} | {record_argument})",
92
+ }
93
+
94
+ return completion_strings.get(self, None)
95
+
96
+ def get_argument(self, record: bool = False) -> str:
97
+ match self:
98
+ case SpectacleMode.REGION:
99
+ return "--record=region" if record else "--region"
100
+ case SpectacleMode.DESKTOP:
101
+ return "--record=screen" if record else "--fullscreen"
102
+ case SpectacleMode.MONITOR:
103
+ return "--record=screen" if record else "--current"
104
+ case SpectacleMode.WINDOW:
105
+ return "--record=window" if record else "--windowundercursor"
106
+ case SpectacleMode.ACTIVEWINDOW:
107
+ return "--record=window" if record else "--activewindow"
108
+ case SpectacleMode.TRANSIENT:
109
+ return "--record=window" if record else "--transientonly"
110
+
111
+
63
112
  @app.command(name="spectacle")
64
113
  @sync
65
114
  async def spectacle(
@@ -92,15 +141,22 @@ async def spectacle(
92
141
  ...,
93
142
  "--object/--text",
94
143
  "-o/-O",
95
- default_factory=sys.stdout.isatty,
144
+ default_factory=lambda: bool(sys.stdout.isatty()),
96
145
  help=(
97
- "Choose how to format the output. If --text (or piped),\n"
98
- "you'll get a link to the uploaded file; if --object (or on a TTY),\n"
146
+ "Specify how to format the output. If --text (or piped), "
147
+ "you'll get a link to the uploaded file; if --object (or on a TTY), "
99
148
  "you'll get the raw Python object."
100
149
  ),
150
+ envvar="ZIPLINE_PRINT_OBJECT",
101
151
  ),
102
152
  ],
103
- record: Annotated[bool, Option("--record", "-r", help="Record a video instead of taking a screenshot.")] = False,
153
+ mode: Annotated[
154
+ SpectacleMode,
155
+ Option(..., "--mode", "-M", help="Specify what mode Spectacle should be launched in.", autocompletion=SpectacleMode.complete_format),
156
+ ] = SpectacleMode.DESKTOP,
157
+ record: Annotated[
158
+ bool, Option(..., "--record", "-r", help="Specify whether or not to record a video instead of taking a screenshot.")
159
+ ] = False,
104
160
  format: Annotated[
105
161
  NameFormat | None,
106
162
  Option(
@@ -213,12 +269,16 @@ async def spectacle(
213
269
  file_path = Path(temp_file.name)
214
270
  temp_file.close()
215
271
 
216
- command: list[str] = ["spectacle", "--nonotify", "--background", "--pointer", "--copy-image", "--output", str(file_path)]
217
-
218
- if record:
219
- command.append("--record=region")
220
- else:
221
- command.append("--region")
272
+ command: list[str] = [
273
+ "spectacle",
274
+ "--nonotify",
275
+ "--background",
276
+ "--pointer",
277
+ mode.get_argument(record),
278
+ "--copy-image",
279
+ "--output",
280
+ str(file_path),
281
+ ]
222
282
 
223
283
  proc = await asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
224
284
  stdout, stderr = await proc.communicate()
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.2.2'
21
- __version_tuple__ = version_tuple = (0, 2, 2)
20
+ __version__ = version = '0.2.4'
21
+ __version_tuple__ = version_tuple = (0, 2, 4)
File without changes
File without changes