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.
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/PKG-INFO +5 -6
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/README.md +2 -2
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/pyproject.toml +6 -3
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/commands/spectacle.py +76 -16
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/version.py +2 -2
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/.gitignore +0 -0
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/LICENSE.md +0 -0
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/__init__.py +0 -0
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/commands/__init__.py +0 -0
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/commands/clipboard.py +0 -0
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/commands/version.py +0 -0
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/entrypoint.py +0 -0
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/sync.py +0 -0
- {tacklebox_cli-0.2.2 → tacklebox_cli-0.2.4}/tacklebox/utils.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tacklebox-cli
|
|
3
|
-
Version: 0.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/
|
|
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.
|
|
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.
|
|
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 = [
|
|
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
|
|
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,
|
|
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",
|
|
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 =
|
|
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
|
-
"
|
|
98
|
-
"you'll get a link to the uploaded file; if --object (or on a TTY)
|
|
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
|
-
|
|
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] = [
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|