mycat 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.
mycat-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: mycat
3
+ Version: 0.1.0
4
+ Summary: Desktop Cat: GTK overlay
5
+ Author: yumiaura
6
+ Keywords: gtk,overlay,desktop,cat,xfce4,gdkpixbuf
7
+ Classifier: Environment :: X11 Applications :: GTK
8
+ Classifier: Intended Audience :: End Users/Desktop
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: PyGObject>=3.40; platform_system != "Windows"
14
+
15
+ ## Desktop Cat for XFCE4 (GTK Overlay)
16
+
17
+ <img src="https://raw.githubusercontent.com/yumiaura/yumiaura/refs/heads/main/images/cat.gif" width="164" alt="cat.gif"/>
18
+
19
+ I made a cute little animated cat for your desktop.<br>
20
+ It’s a lightweight Python + GTK app — no borders, and you can drag it around easily.<br>
21
+ If you like it, maybe I’ll share an [AnimeGirl](https://github.com/yumiaura/mycat/discussions/1) version next time~ 😉<br>
22
+
23
+ ### Install Dependencies
24
+
25
+ ```bash
26
+ sudo apt update
27
+ sudo apt install -y python3 python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-gdkpixbuf-2.0
28
+ ```
29
+
30
+ ### Run
31
+
32
+ ```bash
33
+ python3 main.py
34
+ ```
35
+ ### Run with custom image
36
+
37
+ ```bash
38
+ python3 main.py --image images/cat.png
39
+ ```
40
+
41
+ ### Create animated GIF from sprite sheet
42
+
43
+ ```bash
44
+ sudo apt install imagemagick
45
+ convert images/cat.png -crop 50%x100% +repage -set delay '200,100' -loop 0 images/cat.gif
46
+ ```
47
+
mycat-0.1.0/README.md ADDED
@@ -0,0 +1,33 @@
1
+ ## Desktop Cat for XFCE4 (GTK Overlay)
2
+
3
+ <img src="https://raw.githubusercontent.com/yumiaura/yumiaura/refs/heads/main/images/cat.gif" width="164" alt="cat.gif"/>
4
+
5
+ I made a cute little animated cat for your desktop.<br>
6
+ It’s a lightweight Python + GTK app — no borders, and you can drag it around easily.<br>
7
+ If you like it, maybe I’ll share an [AnimeGirl](https://github.com/yumiaura/mycat/discussions/1) version next time~ 😉<br>
8
+
9
+ ### Install Dependencies
10
+
11
+ ```bash
12
+ sudo apt update
13
+ sudo apt install -y python3 python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-gdkpixbuf-2.0
14
+ ```
15
+
16
+ ### Run
17
+
18
+ ```bash
19
+ python3 main.py
20
+ ```
21
+ ### Run with custom image
22
+
23
+ ```bash
24
+ python3 main.py --image images/cat.png
25
+ ```
26
+
27
+ ### Create animated GIF from sprite sheet
28
+
29
+ ```bash
30
+ sudo apt install imagemagick
31
+ convert images/cat.png -crop 50%x100% +repage -set delay '200,100' -loop 0 images/cat.gif
32
+ ```
33
+
Binary file
mycat-0.1.0/main.py ADDED
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Pixel Cat (from 2-frame PNG sprite), GTK transparent overlay for XFCE
5
+ - Loads a user PNG with TWO frames placed side-by-side (open eyes, closed eyes).
6
+ - Shows a frameless, draggable, always-on-top transparent window.
7
+ - Blink timing: 5 seconds open, 1 second closed, repeating.
8
+ - Right click → Quit.
9
+ - Optional: --image PATH (defaults to ./cat_sprite.png next to this script)
10
+ --size N (scale long side to N px, default from $CAT_SIZE or 160)
11
+ --pos X Y (start position, else restored from config)
12
+
13
+ Dependencies (Ubuntu/Debian):
14
+ sudo apt update
15
+ sudo apt install -y python3-gi gir1.2-gtk-3.0 gir1.2-gdkpixbuf-2.0 python3-gi-cairo
16
+
17
+ """
18
+
19
+ import argparse
20
+ import json
21
+ import gi
22
+ import os
23
+ import sys
24
+ import time
25
+ from pathlib import Path
26
+
27
+ # Require versions before importing from gi.repository
28
+ gi.require_version("Gtk", "3.0")
29
+ gi.require_version("Gdk", "3.0")
30
+ gi.require_version("GdkPixbuf", "2.0")
31
+
32
+ import cairo
33
+ from gi.repository import Gdk
34
+ from gi.repository import GdkPixbuf
35
+ from gi.repository import GLib
36
+ from gi.repository import Gtk
37
+
38
+ # Paths
39
+ SCRIPT_DIR = Path(__file__).resolve().parent
40
+ CFG_DIR = Path.home() / ".config" / "pixelcat"
41
+ CFG_FILE = CFG_DIR / "config.json"
42
+ DEFAULT_SPRITE = SCRIPT_DIR / "images/cat.png"
43
+
44
+
45
+ def parse_args() -> argparse.Namespace:
46
+ p = argparse.ArgumentParser(
47
+ description="Blinking pixel cat overlay (two-frame PNG sprite)."
48
+ )
49
+ p.add_argument(
50
+ "--image",
51
+ "-i",
52
+ type=str,
53
+ default=str(DEFAULT_SPRITE),
54
+ help="Path to PNG sprite with two frames side-by-side (default: %(default)s)",
55
+ )
56
+ env_size = os.environ.get("CAT_SIZE")
57
+ default_size = int(env_size) if (env_size and env_size.isdigit()) else 160
58
+ p.add_argument(
59
+ "--size",
60
+ "-s",
61
+ type=int,
62
+ default=default_size,
63
+ help=(
64
+ "Target size (width of one frame) in pixels (default env CAT_SIZE or 160)"
65
+ ),
66
+ )
67
+ p.add_argument(
68
+ "--pos",
69
+ nargs=2,
70
+ type=int,
71
+ metavar=("X", "Y"),
72
+ help="Start position (overrides remembered position)",
73
+ )
74
+ p.add_argument(
75
+ "--open",
76
+ type=float,
77
+ default=5.0,
78
+ dest="open_sec",
79
+ help="Seconds with eyes open (default: 5.0)",
80
+ )
81
+ p.add_argument(
82
+ "--closed",
83
+ type=float,
84
+ default=1.0,
85
+ dest="closed_sec",
86
+ help="Seconds with eyes closed (default: 1.0)",
87
+ )
88
+ return p.parse_args()
89
+
90
+
91
+ def slice_sprite_to_pixbufs(
92
+ sprite_path: str, target_width: int
93
+ ) -> tuple[GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf]:
94
+ """Load sprite PNG, split into two equal halves, scale to target_width keeping aspect."""
95
+ if not os.path.exists(sprite_path):
96
+ raise FileNotFoundError(f"Sprite not found: {sprite_path}")
97
+ # Load whole sprite
98
+ sprite = GdkPixbuf.Pixbuf.new_from_file(sprite_path)
99
+ sw, sh = sprite.get_width(), sprite.get_height()
100
+ if sw % 2 != 0:
101
+ # If uneven, we still try: second frame assumed same width as first
102
+ frame_w = sw // 2
103
+ else:
104
+ frame_w = sw // 2
105
+ frame_h = sh
106
+
107
+ # Crop first (open), second (closed)
108
+ open_pb = GdkPixbuf.Pixbuf.new_subpixbuf(sprite, 0, 0, frame_w, frame_h)
109
+ closed_pb = GdkPixbuf.Pixbuf.new_subpixbuf(
110
+ sprite, frame_w, 0, sw - frame_w, frame_h
111
+ )
112
+
113
+ # Scale
114
+ if target_width <= 0:
115
+ target_width = frame_w
116
+ scale = target_width / frame_w
117
+ target_height = max(1, int(round(frame_h * scale)))
118
+ interp = GdkPixbuf.InterpType.NEAREST # preserve pixel look
119
+ open_scaled = open_pb.scale_simple(target_width, target_height, interp)
120
+ closed_scaled = closed_pb.scale_simple(target_width, target_height, interp)
121
+ return open_scaled, closed_scaled
122
+
123
+
124
+ class PixelCatWindow(Gtk.Window):
125
+ def __init__(
126
+ self,
127
+ open_pb: GdkPixbuf.Pixbuf,
128
+ closed_pb: GdkPixbuf.Pixbuf,
129
+ args: argparse.Namespace,
130
+ ) -> None:
131
+ super().__init__(type=Gtk.WindowType.TOPLEVEL)
132
+ self.set_app_paintable(True)
133
+ self.set_decorated(False)
134
+ self.set_resizable(False)
135
+ self.set_skip_taskbar_hint(True)
136
+ self.set_skip_pager_hint(True)
137
+ self.set_keep_above(True)
138
+ self.stick()
139
+ self.connect("draw", self.on_draw)
140
+
141
+ screen = self.get_screen()
142
+ visual = screen.get_rgba_visual()
143
+ if visual and screen.is_composited():
144
+ self.set_visual(visual)
145
+ else:
146
+ print(
147
+ "Hint: enable compositor in Xfce (Settings → Window Manager Tweaks → Compositor)."
148
+ )
149
+
150
+ # Input
151
+ self.add_events(
152
+ Gdk.EventMask.BUTTON_PRESS_MASK
153
+ | Gdk.EventMask.BUTTON_RELEASE_MASK
154
+ | Gdk.EventMask.POINTER_MOTION_MASK
155
+ )
156
+ self.connect("button-press-event", self.on_button_press)
157
+ self.connect("button-release-event", self.on_button_release)
158
+ self.connect("motion-notify-event", self.on_motion)
159
+
160
+ # Menu
161
+ self.menu = Gtk.Menu()
162
+ mi_quit = Gtk.MenuItem.new_with_label("Quit")
163
+ mi_quit.connect("activate", lambda *_: Gtk.main_quit())
164
+ self.menu.append(mi_quit)
165
+ self.menu.show_all()
166
+
167
+ # Frames & timing
168
+ self.open_pb = open_pb
169
+ self.closed_pb = closed_pb
170
+ self.current = self.open_pb
171
+ self.open_sec = max(0.05, float(args.open_sec))
172
+ self.closed_sec = max(0.05, float(args.closed_sec))
173
+ self.state = "open"
174
+ self.next_change = time.time() + self.open_sec
175
+
176
+ # Size & position
177
+ self.set_default_size(self.current.get_width(), self.current.get_height())
178
+ if args.pos:
179
+ x, y = args.pos
180
+ self.move(int(x), int(y))
181
+ else:
182
+ self.load_pos()
183
+
184
+ # Ticker (≈60 FPS)
185
+ GLib.timeout_add(16, self.on_tick)
186
+
187
+ def load_pos(self) -> None:
188
+ try:
189
+ if CFG_FILE.exists():
190
+ data = json.loads(CFG_FILE.read_text())
191
+ x, y = int(data.get("x", 100)), int(data.get("y", 100))
192
+ self.move(x, y)
193
+ else:
194
+ self.move(100, 100)
195
+ except Exception as e:
196
+ print("Config load error:", e)
197
+ self.move(100, 100)
198
+
199
+ def save_pos(self) -> None:
200
+ try:
201
+ x, y = self.get_position()
202
+ CFG_DIR.mkdir(parents=True, exist_ok=True)
203
+ CFG_FILE.write_text(json.dumps({"x": x, "y": y}))
204
+ except Exception as e:
205
+ print("Config save error:", e)
206
+
207
+ # Mouse
208
+ def on_button_press(self, widget: Gtk.Widget, event: Gdk.EventButton) -> bool:
209
+ if event.button == 1:
210
+ self.dragging = True
211
+ self.drag_origin = (int(event.x_root), int(event.y_root))
212
+ self.window_pos_at_drag = self.get_position()
213
+ return True
214
+ if event.button == 3:
215
+ self.menu.popup_at_pointer(event)
216
+ return True
217
+ return False
218
+
219
+ def on_motion(self, widget: Gtk.Widget, event: Gdk.EventMotion) -> bool:
220
+ if getattr(self, "dragging", False):
221
+ dx = int(event.x_root) - self.drag_origin[0]
222
+ dy = int(event.y_root) - self.drag_origin[1]
223
+ self.move(self.window_pos_at_drag[0] + dx, self.window_pos_at_drag[1] + dy)
224
+ return True
225
+ return False
226
+
227
+ def on_button_release(self, widget: Gtk.Widget, event: Gdk.EventButton) -> bool:
228
+ if event.button == 1 and getattr(self, "dragging", False):
229
+ self.dragging = False
230
+ self.save_pos()
231
+ return True
232
+ return False
233
+
234
+ def on_tick(self) -> bool:
235
+ now = time.time()
236
+ if now >= self.next_change:
237
+ if self.state == "open":
238
+ self.state = "closed"
239
+ self.current = self.closed_pb
240
+ self.next_change = now + self.closed_sec
241
+ else:
242
+ self.state = "open"
243
+ self.current = self.open_pb
244
+ self.next_change = now + self.open_sec
245
+ self.queue_draw()
246
+ return True
247
+
248
+ def on_draw(self, widget: Gtk.Widget, cr: cairo.Context) -> bool:
249
+ # Clear with transparent
250
+ cr.set_operator(cairo.Operator.SOURCE)
251
+ cr.set_source_rgba(0, 0, 0, 0)
252
+ cr.paint()
253
+ cr.set_operator(cairo.Operator.OVER)
254
+
255
+ win_w = self.get_allocated_width()
256
+ win_h = self.get_allocated_height()
257
+ img_w = self.current.get_width()
258
+ img_h = self.current.get_height()
259
+ x = (win_w - img_w) // 2
260
+ y = (win_h - img_h) // 2
261
+ Gdk.cairo_set_source_pixbuf(cr, self.current, x, y)
262
+ cr.paint()
263
+ return False
264
+
265
+
266
+ def ensure_sprite_exists(sprite_path: Path) -> None:
267
+ """Ensure sprite file exists; create parent directory if needed (no-op if present)."""
268
+ # Copy bundled demo file if user didn't provide one (placeholder; intentionally a no-op here)
269
+ try:
270
+ if not sprite_path.exists():
271
+ sprite_path.parent.mkdir(parents=True, exist_ok=True)
272
+ # If running from a sandbox, a sample could be copied here.
273
+ raise FileNotFoundError
274
+ except Exception:
275
+ pass
276
+
277
+
278
+ def main() -> None:
279
+ args = parse_args()
280
+ sprite_path = Path(args.image).expanduser()
281
+ if not sprite_path.exists():
282
+ print(f"File not found: {sprite_path}")
283
+ sys.exit(1)
284
+ try:
285
+ open_pb, closed_pb = slice_sprite_to_pixbufs(str(sprite_path), args.size)
286
+ except Exception as e:
287
+ print("Error loading sprite:", e)
288
+ sys.exit(2)
289
+ win = PixelCatWindow(open_pb, closed_pb, args)
290
+ win.show_all()
291
+ Gtk.main()
292
+
293
+
294
+ if __name__ == "__main__":
295
+ main()
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: mycat
3
+ Version: 0.1.0
4
+ Summary: Desktop Cat: GTK overlay
5
+ Author: yumiaura
6
+ Keywords: gtk,overlay,desktop,cat,xfce4,gdkpixbuf
7
+ Classifier: Environment :: X11 Applications :: GTK
8
+ Classifier: Intended Audience :: End Users/Desktop
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: PyGObject>=3.40; platform_system != "Windows"
14
+
15
+ ## Desktop Cat for XFCE4 (GTK Overlay)
16
+
17
+ <img src="https://raw.githubusercontent.com/yumiaura/yumiaura/refs/heads/main/images/cat.gif" width="164" alt="cat.gif"/>
18
+
19
+ I made a cute little animated cat for your desktop.<br>
20
+ It’s a lightweight Python + GTK app — no borders, and you can drag it around easily.<br>
21
+ If you like it, maybe I’ll share an [AnimeGirl](https://github.com/yumiaura/mycat/discussions/1) version next time~ 😉<br>
22
+
23
+ ### Install Dependencies
24
+
25
+ ```bash
26
+ sudo apt update
27
+ sudo apt install -y python3 python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-gdkpixbuf-2.0
28
+ ```
29
+
30
+ ### Run
31
+
32
+ ```bash
33
+ python3 main.py
34
+ ```
35
+ ### Run with custom image
36
+
37
+ ```bash
38
+ python3 main.py --image images/cat.png
39
+ ```
40
+
41
+ ### Create animated GIF from sprite sheet
42
+
43
+ ```bash
44
+ sudo apt install imagemagick
45
+ convert images/cat.png -crop 50%x100% +repage -set delay '200,100' -loop 0 images/cat.gif
46
+ ```
47
+
@@ -0,0 +1,10 @@
1
+ README.md
2
+ main.py
3
+ pyproject.toml
4
+ images/cat.png
5
+ mycat.egg-info/PKG-INFO
6
+ mycat.egg-info/SOURCES.txt
7
+ mycat.egg-info/dependency_links.txt
8
+ mycat.egg-info/entry_points.txt
9
+ mycat.egg-info/requires.txt
10
+ mycat.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mycat = main:main
@@ -0,0 +1,3 @@
1
+
2
+ [:platform_system != "Windows"]
3
+ PyGObject>=3.40
@@ -0,0 +1 @@
1
+ main
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mycat"
7
+ version = "0.1.0"
8
+ description = "Desktop Cat: GTK overlay"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ authors = [{ name = "yumiaura" }]
12
+ keywords = ["gtk", "overlay", "desktop", "cat", "xfce4", "gdkpixbuf"]
13
+ classifiers = [
14
+ "Environment :: X11 Applications :: GTK",
15
+ "Intended Audience :: End Users/Desktop",
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: POSIX :: Linux",
18
+ ]
19
+ dependencies = [
20
+ # PyGObject/GTK3
21
+ "PyGObject>=3.40; platform_system != 'Windows'"
22
+ ]
23
+
24
+ [project.scripts]
25
+ mycat = "main:main"
26
+
27
+ [tool.setuptools]
28
+ py-modules = ["main"]
29
+ include-package-data = true
30
+
31
+ [tool.setuptools.data-files]
32
+ "share/mycat/images" = ["images/*"]
mycat-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+