clipsy 1.8.0__py3-none-any.whl → 1.9.0__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.
clipsy/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "1.8.0"
1
+ __version__ = "1.9.0"
2
2
  __app_name__ = "Clipsy"
clipsy/app.py CHANGED
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  import rumps
6
6
 
7
7
  from clipsy import __version__
8
- from clipsy.config import DB_PATH, IMAGE_DIR, MENU_DISPLAY_COUNT, POLL_INTERVAL, REDACT_SENSITIVE, THUMBNAIL_SIZE
8
+ from clipsy.config import DB_PATH, IMAGE_DIR, MAX_PINNED_ENTRIES, MENU_DISPLAY_COUNT, POLL_INTERVAL, REDACT_SENSITIVE, THUMBNAIL_SIZE
9
9
  from clipsy.models import ClipboardEntry, ContentType
10
10
  from clipsy.monitor import ClipboardMonitor
11
11
  from clipsy.storage import StorageManager
@@ -34,10 +34,24 @@ class ClipsyApp(rumps.App):
34
34
  None, # separator
35
35
  ]
36
36
 
37
- entries = self._storage.get_recent(limit=MENU_DISPLAY_COUNT)
38
37
  self._entry_ids.clear()
39
38
 
40
- if not entries:
39
+ # Add pinned submenu if there are pinned entries
40
+ pinned_entries = self._storage.get_pinned()
41
+ if pinned_entries:
42
+ pinned_menu = rumps.MenuItem("📌 Pinned")
43
+ for entry in pinned_entries:
44
+ pinned_menu.add(self._create_entry_menu_item(entry))
45
+ pinned_menu.add(None) # separator
46
+ pinned_menu.add(rumps.MenuItem("Clear Pinned", callback=self._on_clear_pinned))
47
+ self.menu.add(pinned_menu)
48
+ self.menu.add(None) # separator
49
+
50
+ entries = self._storage.get_recent(limit=MENU_DISPLAY_COUNT)
51
+ # Filter out pinned entries from recent list
52
+ entries = [e for e in entries if not e.pinned]
53
+
54
+ if not entries and not pinned_entries:
41
55
  self.menu.add(rumps.MenuItem("(No clipboard history)", callback=None))
42
56
  else:
43
57
  for entry in entries:
@@ -118,6 +132,17 @@ class ClipsyApp(rumps.App):
118
132
  if entry is None:
119
133
  return
120
134
 
135
+ # Check if Option key is held (for pin toggle)
136
+ try:
137
+ from AppKit import NSAlternateKeyMask, NSEvent
138
+
139
+ modifier_flags = NSEvent.modifierFlags()
140
+ if modifier_flags & NSAlternateKeyMask:
141
+ self._on_pin_toggle(entry)
142
+ return
143
+ except Exception:
144
+ pass # If we can't check modifiers, proceed with normal copy
145
+
121
146
  try:
122
147
  from AppKit import NSPasteboard, NSPasteboardTypePNG, NSPasteboardTypeString
123
148
  from Foundation import NSData
@@ -161,6 +186,32 @@ class ClipsyApp(rumps.App):
161
186
  except Exception:
162
187
  logger.exception("Error copying entry to clipboard")
163
188
 
189
+ def _on_pin_toggle(self, entry: ClipboardEntry) -> None:
190
+ """Toggle pin status for an entry."""
191
+ if entry.pinned:
192
+ # Unpin
193
+ self._storage.toggle_pin(entry.id)
194
+ rumps.notification("Clipsy", "", "Unpinned", sound=False)
195
+ else:
196
+ # Check if we can pin (not sensitive, under limit)
197
+ if entry.is_sensitive:
198
+ rumps.notification("Clipsy", "", "Cannot pin sensitive data", sound=False)
199
+ return
200
+
201
+ if self._storage.count_pinned() >= MAX_PINNED_ENTRIES:
202
+ rumps.notification("Clipsy", "", f"Maximum {MAX_PINNED_ENTRIES} pinned items", sound=False)
203
+ return
204
+
205
+ self._storage.toggle_pin(entry.id)
206
+ rumps.notification("Clipsy", "", "Pinned", sound=False)
207
+
208
+ self._refresh_menu()
209
+
210
+ def _on_clear_pinned(self, _sender) -> None:
211
+ """Clear all pinned entries."""
212
+ self._storage.clear_pinned()
213
+ self._refresh_menu()
214
+
164
215
  def _on_search(self, _sender) -> None:
165
216
  response = rumps.Window(
166
217
  message="Search clipboard history:",
clipsy/config.py CHANGED
@@ -25,3 +25,4 @@ def _parse_menu_display_count() -> int:
25
25
  MENU_DISPLAY_COUNT = _parse_menu_display_count()
26
26
  THUMBNAIL_SIZE = (32, 32) # pixels, for menu icon display
27
27
  REDACT_SENSITIVE = True # mask sensitive data in preview (API keys, passwords, etc.)
28
+ MAX_PINNED_ENTRIES = 5 # maximum number of pinned entries allowed
clipsy/storage.py CHANGED
@@ -211,6 +211,21 @@ class StorageManager:
211
211
  row = self._conn.execute("SELECT COUNT(*) as cnt FROM clipboard_entries").fetchone()
212
212
  return row["cnt"]
213
213
 
214
+ def get_pinned(self) -> list[ClipboardEntry]:
215
+ rows = self._conn.execute(
216
+ "SELECT * FROM clipboard_entries WHERE pinned = 1 ORDER BY created_at DESC"
217
+ ).fetchall()
218
+ return [self._row_to_entry(r) for r in rows]
219
+
220
+ def count_pinned(self) -> int:
221
+ row = self._conn.execute("SELECT COUNT(*) as cnt FROM clipboard_entries WHERE pinned = 1").fetchone()
222
+ return row["cnt"]
223
+
224
+ def clear_pinned(self) -> None:
225
+ """Unpin all pinned entries."""
226
+ self._conn.execute("UPDATE clipboard_entries SET pinned = 0 WHERE pinned = 1")
227
+ self._conn.commit()
228
+
214
229
  def close(self) -> None:
215
230
  self._conn.close()
216
231
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clipsy
3
- Version: 1.8.0
3
+ Version: 1.9.0
4
4
  Summary: Lightweight clipboard history manager for macOS
5
5
  Author-email: Brendan Conrad <brendan.conrad@gmail.com>
6
6
  License: MIT
@@ -46,6 +46,8 @@ A lightweight clipboard history manager for macOS. Runs as a menu bar icon — n
46
46
  - **Image thumbnails** — Visual previews for copied images in the menu
47
47
  - **Sensitive data masking** — Auto-detects API keys, passwords, SSNs, credit cards, private keys, and tokens; displays masked previews with 🔒 icon
48
48
  - **Search** — Full-text search across all clipboard entries (SQLite FTS5)
49
+ - **Rich text preservation** — Preserves RTF and HTML formatting when re-copying from history (e.g., bold, italic, links from web pages)
50
+ - **Pin favorites** — Option-click to pin up to 5 frequently-used snippets (sensitive data cannot be pinned)
49
51
  - **Click to re-copy** — Click any entry in the menu to put it back on your clipboard
50
52
  - **Deduplication** — Copying the same content twice bumps it to the top instead of creating a duplicate
51
53
  - **Auto-purge** — Keeps the most recent 500 entries, automatically cleans up old ones
@@ -103,11 +105,15 @@ Then just use your Mac normally. Every time you copy something, it shows up in t
103
105
  ├── ──────────────────
104
106
  ├── Search...
105
107
  ├── ──────────────────
108
+ ├── 📌 Pinned ►
109
+ │ ├── "my-api-endpoint.com/v1..."
110
+ │ └── "SELECT * FROM users..."
111
+ ├── ──────────────────
106
112
  ├── "Meeting notes for Q3 plan..."
107
113
  ├── "https://github.com/example..."
108
114
  ├── 🔒 "password=••••••••"
109
115
  ├── [thumbnail] "[Image: 1920x1080]"
110
- ├── ... (up to 10 items)
116
+ ├── ... (up to 10 items, configurable)
111
117
  ├── ──────────────────
112
118
  ├── Clear History
113
119
  ├── ──────────────────
@@ -116,6 +122,8 @@ Then just use your Mac normally. Every time you copy something, it shows up in t
116
122
  └── Quit Clipsy
117
123
  ```
118
124
 
125
+ **Tip:** Hold **Option (⌥)** while clicking an entry to pin/unpin it.
126
+
119
127
  ## Commands
120
128
 
121
129
  ```bash
@@ -125,6 +133,17 @@ clipsy uninstall # Remove from login items
125
133
  clipsy run # Run in foreground (for debugging)
126
134
  ```
127
135
 
136
+ ## Configuration
137
+
138
+ | Variable | Default | Range | Description |
139
+ |----------|---------|-------|-------------|
140
+ | `CLIPSY_MENU_DISPLAY_COUNT` | `10` | 5–50 | Number of entries shown in the menu |
141
+
142
+ ```bash
143
+ # Example: show 20 entries in the menu
144
+ export CLIPSY_MENU_DISPLAY_COUNT=20
145
+ ```
146
+
128
147
  ## Data Storage
129
148
 
130
149
  All data is stored in `~/.local/share/clipsy/`:
@@ -0,0 +1,15 @@
1
+ clipsy/__init__.py,sha256=AnsrZyNwA76HMWFc_Q7iDYH8Citiqm2lXLiZFzPs7uQ,46
2
+ clipsy/__main__.py,sha256=AzUjrEhnuVX5Mkk3QhfmAO17cyvIYOABsz7QWYmw1r8,4991
3
+ clipsy/app.py,sha256=scM-po0Kw1SOg_pDlMxsgbzhDQWS4BAvv5Iq64GLJEc,9861
4
+ clipsy/config.py,sha256=NOElM7jJ73AWQ1sf09dxg82-1nGErXd1zT63v2MPGpA,980
5
+ clipsy/models.py,sha256=9wuaq1t9HhaH8QYuieN7fEVM_RTYEvv6ZUCM_InOMKk,632
6
+ clipsy/monitor.py,sha256=PBTxK53arFm5VVCHIYlxz8eoYQrgTwWsIWNE49IE7oU,6904
7
+ clipsy/redact.py,sha256=EU2nq8oEDYLZ_I_9tX2bXACIF971a0V_ApKdvpN4VGY,7284
8
+ clipsy/storage.py,sha256=WE3EeOLcSgw-0it1qPk5rS-vDRC3-XDILaMZquBFR5Y,10659
9
+ clipsy/utils.py,sha256=rztT20cXYJ7633q3oLwt6Fv0Ubq9hm5bPPKDkWyvxdI,2208
10
+ clipsy-1.9.0.dist-info/licenses/LICENSE,sha256=bZ7uVihgPnLGryi5ugUgkVrEoho0_hFAd3bVdhYXGqs,1071
11
+ clipsy-1.9.0.dist-info/METADATA,sha256=tJ8uQW6-pRU2JAyAnO3dpr_ix9zFUpS6NvizCYZAD4Y,6680
12
+ clipsy-1.9.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
+ clipsy-1.9.0.dist-info/entry_points.txt,sha256=ISIoWU3Zj-4NEuj2pJW96LxpsvjRJJH73otv9gA9YSs,48
14
+ clipsy-1.9.0.dist-info/top_level.txt,sha256=trxprVJk4ZMudCshc7PD0N9iFgQO4Tq4sW5L5wLduns,7
15
+ clipsy-1.9.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- clipsy/__init__.py,sha256=2M96CO-qBjhCYnYgi1FZuen5WyQOvlbtLUIIPCDsES0,46
2
- clipsy/__main__.py,sha256=AzUjrEhnuVX5Mkk3QhfmAO17cyvIYOABsz7QWYmw1r8,4991
3
- clipsy/app.py,sha256=e987geHCfDs3_9GulTZaS2iJT-BUL_iwpNtAmt6kPW8,7786
4
- clipsy/config.py,sha256=YetSNZkSkSOHYnCZpty6sGeG99MajbN4goL8UkzJ62A,913
5
- clipsy/models.py,sha256=9wuaq1t9HhaH8QYuieN7fEVM_RTYEvv6ZUCM_InOMKk,632
6
- clipsy/monitor.py,sha256=PBTxK53arFm5VVCHIYlxz8eoYQrgTwWsIWNE49IE7oU,6904
7
- clipsy/redact.py,sha256=EU2nq8oEDYLZ_I_9tX2bXACIF971a0V_ApKdvpN4VGY,7284
8
- clipsy/storage.py,sha256=ZcuW2uFvJM0Cd3UQEWIMSZYH2DYWM6lD6yol6VpM5io,10047
9
- clipsy/utils.py,sha256=rztT20cXYJ7633q3oLwt6Fv0Ubq9hm5bPPKDkWyvxdI,2208
10
- clipsy-1.8.0.dist-info/licenses/LICENSE,sha256=bZ7uVihgPnLGryi5ugUgkVrEoho0_hFAd3bVdhYXGqs,1071
11
- clipsy-1.8.0.dist-info/METADATA,sha256=UMKIznK4wUar5eV0ic7QVLuoRdZN4fMT6AL34BbuHdE,5884
12
- clipsy-1.8.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
- clipsy-1.8.0.dist-info/entry_points.txt,sha256=ISIoWU3Zj-4NEuj2pJW96LxpsvjRJJH73otv9gA9YSs,48
14
- clipsy-1.8.0.dist-info/top_level.txt,sha256=trxprVJk4ZMudCshc7PD0N9iFgQO4Tq4sW5L5wLduns,7
15
- clipsy-1.8.0.dist-info/RECORD,,
File without changes