linux-copycache 1.0.1__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.
- linux_copycache-1.0.1/PKG-INFO +75 -0
- linux_copycache-1.0.1/README.md +48 -0
- linux_copycache-1.0.1/setup.cfg +4 -0
- linux_copycache-1.0.1/setup.py +36 -0
- linux_copycache-1.0.1/src/linux_clipboard_manager/__init__.py +5 -0
- linux_copycache-1.0.1/src/linux_clipboard_manager/clipboard_daemon.py +34 -0
- linux_copycache-1.0.1/src/linux_clipboard_manager/clipboard_gui.py +268 -0
- linux_copycache-1.0.1/src/linux_clipboard_manager/db.py +70 -0
- linux_copycache-1.0.1/src/linux_copycache.egg-info/PKG-INFO +75 -0
- linux_copycache-1.0.1/src/linux_copycache.egg-info/SOURCES.txt +12 -0
- linux_copycache-1.0.1/src/linux_copycache.egg-info/dependency_links.txt +1 -0
- linux_copycache-1.0.1/src/linux_copycache.egg-info/entry_points.txt +2 -0
- linux_copycache-1.0.1/src/linux_copycache.egg-info/requires.txt +2 -0
- linux_copycache-1.0.1/src/linux_copycache.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linux-copycache
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: A lightweight, fast, and reliable clipboard manager for Linux (Ubuntu) mimicking Win+V.
|
|
5
|
+
Home-page: https://github.com/randhana/linux-clipboard-manager
|
|
6
|
+
Author: Pulathisi Kariyawasam
|
|
7
|
+
Author-email: pulathisi.kariyawasam@gmail.com
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/randhana/linux-clipboard-manager/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: PyQt6>=6.4.0
|
|
16
|
+
Requires-Dist: pyperclip>=1.8.2
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: project-url
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# Linux Clipboard Manager
|
|
29
|
+
|
|
30
|
+
A lightweight, local-only clipboard manager for Linux built with Python and PyQt6.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
- Background daemon to monitor clipboard history.
|
|
34
|
+
- Popup GUI to browse and search previous items.
|
|
35
|
+
- Auto-paste integration with `xdotool` (X11) or `wtype` (Wayland).
|
|
36
|
+
|
|
37
|
+
## Prerequisites
|
|
38
|
+
- **Python 3**
|
|
39
|
+
- **xdotool** (for X11 users) or **wtype** (for Wayland users)
|
|
40
|
+
|
|
41
|
+
You can install `wtype` or `xdotool` on Ubuntu using:
|
|
42
|
+
```bash
|
|
43
|
+
sudo apt install xdotool wtype
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
You can install the Clipboard Manager easily via `pip`:
|
|
48
|
+
```bash
|
|
49
|
+
pip install linux-clipboard-manager
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### System Dependencies
|
|
53
|
+
The auto-pasting functionality requires `xdotool` and `xclip` to be installed on your system.
|
|
54
|
+
```bash
|
|
55
|
+
sudo apt install xdotool xclip
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Running the tool
|
|
59
|
+
Once installed, you can start the application from anywhere in your terminal:
|
|
60
|
+
```bash
|
|
61
|
+
linux-clipboard
|
|
62
|
+
```
|
|
63
|
+
The application will launch, dock itself to your system tray, and quietly monitor your copied text!
|
|
64
|
+
|
|
65
|
+
## Setting up the `Super+V` (Win+V) Shortcut in Ubuntu
|
|
66
|
+
For the native Windows experience, set up a custom shortcut:
|
|
67
|
+
1. Open **Settings** -> **Keyboard**.
|
|
68
|
+
2. Scroll to the bottom and click **View and Customize Shortcuts**.
|
|
69
|
+
3. Select **Custom Shortcuts** and click the **+** or **Add Shortcut** button.
|
|
70
|
+
4. **Name:** `Clipboard Manager`
|
|
71
|
+
5. **Command:** `linux-clipboard`
|
|
72
|
+
6. **Shortcut:** Press `Super + V` (Windows key + V).
|
|
73
|
+
7. Click **Add**.
|
|
74
|
+
|
|
75
|
+
Now, whenever you press `Super + V`, the history window will pop up!
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Linux Clipboard Manager
|
|
2
|
+
|
|
3
|
+
A lightweight, local-only clipboard manager for Linux built with Python and PyQt6.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Background daemon to monitor clipboard history.
|
|
7
|
+
- Popup GUI to browse and search previous items.
|
|
8
|
+
- Auto-paste integration with `xdotool` (X11) or `wtype` (Wayland).
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
- **Python 3**
|
|
12
|
+
- **xdotool** (for X11 users) or **wtype** (for Wayland users)
|
|
13
|
+
|
|
14
|
+
You can install `wtype` or `xdotool` on Ubuntu using:
|
|
15
|
+
```bash
|
|
16
|
+
sudo apt install xdotool wtype
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
You can install the Clipboard Manager easily via `pip`:
|
|
21
|
+
```bash
|
|
22
|
+
pip install linux-clipboard-manager
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### System Dependencies
|
|
26
|
+
The auto-pasting functionality requires `xdotool` and `xclip` to be installed on your system.
|
|
27
|
+
```bash
|
|
28
|
+
sudo apt install xdotool xclip
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Running the tool
|
|
32
|
+
Once installed, you can start the application from anywhere in your terminal:
|
|
33
|
+
```bash
|
|
34
|
+
linux-clipboard
|
|
35
|
+
```
|
|
36
|
+
The application will launch, dock itself to your system tray, and quietly monitor your copied text!
|
|
37
|
+
|
|
38
|
+
## Setting up the `Super+V` (Win+V) Shortcut in Ubuntu
|
|
39
|
+
For the native Windows experience, set up a custom shortcut:
|
|
40
|
+
1. Open **Settings** -> **Keyboard**.
|
|
41
|
+
2. Scroll to the bottom and click **View and Customize Shortcuts**.
|
|
42
|
+
3. Select **Custom Shortcuts** and click the **+** or **Add Shortcut** button.
|
|
43
|
+
4. **Name:** `Clipboard Manager`
|
|
44
|
+
5. **Command:** `linux-clipboard`
|
|
45
|
+
6. **Shortcut:** Press `Super + V` (Windows key + V).
|
|
46
|
+
7. Click **Add**.
|
|
47
|
+
|
|
48
|
+
Now, whenever you press `Super + V`, the history window will pop up!
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import setuptools
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setuptools.setup(
|
|
7
|
+
name="linux-copycache",
|
|
8
|
+
version="1.0.1",
|
|
9
|
+
author="Pulathisi Kariyawasam",
|
|
10
|
+
author_email="pulathisi.kariyawasam@gmail.com",
|
|
11
|
+
description="A lightweight, fast, and reliable clipboard manager for Linux (Ubuntu) mimicking Win+V.",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/randhana/linux-clipboard-manager",
|
|
15
|
+
project_urls={
|
|
16
|
+
"Bug Tracker": "https://github.com/randhana/linux-clipboard-manager/issues",
|
|
17
|
+
},
|
|
18
|
+
classifiers=[
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Environment :: X11 Applications :: Qt",
|
|
23
|
+
],
|
|
24
|
+
package_dir={"": "src"},
|
|
25
|
+
packages=setuptools.find_packages(where="src"),
|
|
26
|
+
python_requires=">=3.8",
|
|
27
|
+
install_requires=[
|
|
28
|
+
"PyQt6>=6.4.0",
|
|
29
|
+
"pyperclip>=1.8.2",
|
|
30
|
+
],
|
|
31
|
+
entry_points={
|
|
32
|
+
"console_scripts": [
|
|
33
|
+
"linux-clipboard=linux_clipboard_manager.clipboard_gui:main",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from PyQt6.QtWidgets import QApplication
|
|
3
|
+
from PyQt6.QtGui import QClipboard
|
|
4
|
+
import db
|
|
5
|
+
|
|
6
|
+
class ClipboardDaemon:
|
|
7
|
+
def __init__(self, app):
|
|
8
|
+
self.app = app
|
|
9
|
+
self.clipboard = app.clipboard()
|
|
10
|
+
self.clipboard.dataChanged.connect(self.on_clipboard_change)
|
|
11
|
+
|
|
12
|
+
# Store initial value
|
|
13
|
+
text = self.clipboard.text()
|
|
14
|
+
if text:
|
|
15
|
+
db.add_item(text)
|
|
16
|
+
|
|
17
|
+
def on_clipboard_change(self):
|
|
18
|
+
text = self.clipboard.text()
|
|
19
|
+
if text:
|
|
20
|
+
db.add_item(text)
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
app = QApplication(sys.argv)
|
|
24
|
+
app.setQuitOnLastWindowClosed(False)
|
|
25
|
+
|
|
26
|
+
# Initialize DB before starting
|
|
27
|
+
db.init_db()
|
|
28
|
+
|
|
29
|
+
daemon = ClipboardDaemon(app)
|
|
30
|
+
print("Clipboard daemon started.")
|
|
31
|
+
sys.exit(app.exec())
|
|
32
|
+
|
|
33
|
+
if __name__ == '__main__':
|
|
34
|
+
main()
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
from PyQt6.QtWidgets import (
|
|
6
|
+
QApplication, QWidget, QVBoxLayout, QListWidget, QListWidgetItem, QLabel,
|
|
7
|
+
QSystemTrayIcon, QMenu
|
|
8
|
+
)
|
|
9
|
+
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
|
|
10
|
+
from PyQt6.QtGui import QFont, QKeyEvent, QIcon, QClipboard
|
|
11
|
+
|
|
12
|
+
from . import db
|
|
13
|
+
|
|
14
|
+
def simulate_paste():
|
|
15
|
+
# Wait a tiny bit for the GUI window to close and focus to return to previous window
|
|
16
|
+
time.sleep(0.1)
|
|
17
|
+
|
|
18
|
+
session_type = os.environ.get('XDG_SESSION_TYPE', '').lower()
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
if session_type == 'wayland':
|
|
22
|
+
# wtype simulates keystrokes in Wayland
|
|
23
|
+
subprocess.run(['wtype', '-M', 'ctrl', '-k', 'v', '-m', 'ctrl'], check=False)
|
|
24
|
+
# fallback if wtype isn't installed: skip or try ydotool
|
|
25
|
+
else:
|
|
26
|
+
# X11 fallback
|
|
27
|
+
subprocess.run(['xdotool', 'key', 'ctrl+v'], check=False)
|
|
28
|
+
except FileNotFoundError:
|
|
29
|
+
print("wtype or xdotool not found. Auto-paste will not work.")
|
|
30
|
+
|
|
31
|
+
class ClipboardGUI(QWidget):
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__()
|
|
34
|
+
# Frameless, kept on top, and doesn't steal focus from other apps
|
|
35
|
+
self.setWindowFlags(
|
|
36
|
+
Qt.WindowType.FramelessWindowHint |
|
|
37
|
+
Qt.WindowType.WindowStaysOnTopHint |
|
|
38
|
+
Qt.WindowType.Tool
|
|
39
|
+
)
|
|
40
|
+
self.resize(400, 500)
|
|
41
|
+
self.setStyleSheet("""
|
|
42
|
+
QWidget {
|
|
43
|
+
background-color: #1e1e2e;
|
|
44
|
+
color: #cdd6f4;
|
|
45
|
+
border: 1px solid #45475a;
|
|
46
|
+
border-radius: 8px;
|
|
47
|
+
}
|
|
48
|
+
QLabel {
|
|
49
|
+
padding: 10px;
|
|
50
|
+
font-weight: bold;
|
|
51
|
+
font-size: 14px;
|
|
52
|
+
background-color: #313244;
|
|
53
|
+
border-bottom: 1px solid #45475a;
|
|
54
|
+
border-top-left-radius: 8px;
|
|
55
|
+
border-top-right-radius: 8px;
|
|
56
|
+
}
|
|
57
|
+
QListWidget {
|
|
58
|
+
border: none;
|
|
59
|
+
outline: none;
|
|
60
|
+
margin: 5px;
|
|
61
|
+
}
|
|
62
|
+
QListWidget::item {
|
|
63
|
+
padding: 10px;
|
|
64
|
+
border-bottom: 1px solid #313244;
|
|
65
|
+
}
|
|
66
|
+
QListWidget::item:selected {
|
|
67
|
+
background-color: #89b4fa;
|
|
68
|
+
color: #1e1e2e;
|
|
69
|
+
border-radius: 4px;
|
|
70
|
+
}
|
|
71
|
+
""")
|
|
72
|
+
|
|
73
|
+
layout = QVBoxLayout(self)
|
|
74
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
75
|
+
layout.setSpacing(0)
|
|
76
|
+
|
|
77
|
+
title = QLabel("Clipboard History")
|
|
78
|
+
layout.addWidget(title)
|
|
79
|
+
|
|
80
|
+
self.list_widget = QListWidget()
|
|
81
|
+
font = QFont("Sans Serif", 11)
|
|
82
|
+
self.list_widget.setFont(font)
|
|
83
|
+
self.list_widget.itemClicked.connect(self.on_item_clicked)
|
|
84
|
+
self.list_widget.currentItemChanged.connect(self.on_current_item_changed)
|
|
85
|
+
self.load_items()
|
|
86
|
+
|
|
87
|
+
# Flag for tracking internal updates so we don't reload list
|
|
88
|
+
self.internal_clipboard_update_text = None
|
|
89
|
+
layout.addWidget(self.list_widget)
|
|
90
|
+
|
|
91
|
+
# Auto-hide timer
|
|
92
|
+
self.hide_timer = QTimer(self)
|
|
93
|
+
self.hide_timer.timeout.connect(self.hide)
|
|
94
|
+
self.hide_timer.setInterval(60000) # 1 minute of inactivity
|
|
95
|
+
|
|
96
|
+
# Add old_pos for window dragging
|
|
97
|
+
self.old_pos = None
|
|
98
|
+
|
|
99
|
+
def on_current_item_changed(self, current, previous):
|
|
100
|
+
if current:
|
|
101
|
+
self.reset_hide_timer()
|
|
102
|
+
content = current.data(Qt.ItemDataRole.UserRole)
|
|
103
|
+
self.internal_clipboard_update_text = content
|
|
104
|
+
|
|
105
|
+
# Update clipboard silently
|
|
106
|
+
clipboard = QApplication.clipboard()
|
|
107
|
+
clipboard.setText(content, QClipboard.Mode.Clipboard)
|
|
108
|
+
if clipboard.supportsSelection():
|
|
109
|
+
clipboard.setText(content, QClipboard.Mode.Selection)
|
|
110
|
+
|
|
111
|
+
import pyperclip
|
|
112
|
+
try:
|
|
113
|
+
pyperclip.copy(content)
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
def mousePressEvent(self, event):
|
|
118
|
+
self.reset_hide_timer()
|
|
119
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
|
120
|
+
self.old_pos = event.globalPosition().toPoint()
|
|
121
|
+
|
|
122
|
+
def mouseMoveEvent(self, event):
|
|
123
|
+
self.reset_hide_timer()
|
|
124
|
+
if self.old_pos is not None:
|
|
125
|
+
delta = event.globalPosition().toPoint() - self.old_pos
|
|
126
|
+
self.move(self.pos() + delta)
|
|
127
|
+
self.old_pos = event.globalPosition().toPoint()
|
|
128
|
+
|
|
129
|
+
def mouseReleaseEvent(self, event):
|
|
130
|
+
self.reset_hide_timer()
|
|
131
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
|
132
|
+
self.old_pos = None
|
|
133
|
+
|
|
134
|
+
def on_clipboard_change(self):
|
|
135
|
+
text = self.clipboard.text()
|
|
136
|
+
if text:
|
|
137
|
+
db.add_item(text)
|
|
138
|
+
if self.isVisible():
|
|
139
|
+
self.load_items()
|
|
140
|
+
|
|
141
|
+
def showEvent(self, event):
|
|
142
|
+
self.reset_hide_timer()
|
|
143
|
+
self.load_items()
|
|
144
|
+
super().showEvent(event)
|
|
145
|
+
|
|
146
|
+
def reset_hide_timer(self):
|
|
147
|
+
if self.isVisible():
|
|
148
|
+
self.hide_timer.start()
|
|
149
|
+
|
|
150
|
+
def hideEvent(self, event):
|
|
151
|
+
self.hide_timer.stop()
|
|
152
|
+
super().hideEvent(event)
|
|
153
|
+
|
|
154
|
+
def load_items(self):
|
|
155
|
+
self.list_widget.clear()
|
|
156
|
+
items = db.get_items(50)
|
|
157
|
+
for item_id, content in items:
|
|
158
|
+
# Truncate content for display if too long, replace newlines
|
|
159
|
+
display_text = content.replace('\n', ' \N{RETURN SYMBOL} ')
|
|
160
|
+
if len(display_text) > 200:
|
|
161
|
+
display_text = display_text[:200] + "..."
|
|
162
|
+
|
|
163
|
+
list_item = QListWidgetItem(display_text)
|
|
164
|
+
list_item.setData(Qt.ItemDataRole.UserRole, content)
|
|
165
|
+
self.list_widget.addItem(list_item)
|
|
166
|
+
|
|
167
|
+
if self.list_widget.count() > 0:
|
|
168
|
+
self.list_widget.setCurrentRow(0)
|
|
169
|
+
|
|
170
|
+
def on_item_clicked(self, item):
|
|
171
|
+
self.perform_paste(item)
|
|
172
|
+
|
|
173
|
+
def keyPressEvent(self, event: QKeyEvent):
|
|
174
|
+
self.reset_hide_timer()
|
|
175
|
+
if event.key() == Qt.Key.Key_Escape:
|
|
176
|
+
self.hide()
|
|
177
|
+
elif event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
|
178
|
+
item = self.list_widget.currentItem()
|
|
179
|
+
if item:
|
|
180
|
+
self.perform_paste(item)
|
|
181
|
+
else:
|
|
182
|
+
super().keyPressEvent(event)
|
|
183
|
+
|
|
184
|
+
def perform_paste(self, item):
|
|
185
|
+
content = item.data(Qt.ItemDataRole.UserRole)
|
|
186
|
+
# Update DB to bring it to top
|
|
187
|
+
db.add_item(content)
|
|
188
|
+
|
|
189
|
+
self.on_current_item_changed(item, None)
|
|
190
|
+
|
|
191
|
+
# We no longer hide automatically or auto-paste natively here
|
|
192
|
+
# so the user can easily select multiple items and paste manually
|
|
193
|
+
# whenever they decide to switch back to their app.
|
|
194
|
+
|
|
195
|
+
class ClipboardMonitor:
|
|
196
|
+
def __init__(self, clipboard, gui_instance):
|
|
197
|
+
self.clipboard = clipboard
|
|
198
|
+
self.gui = gui_instance
|
|
199
|
+
self.clipboard.dataChanged.connect(self.on_clipboard_change)
|
|
200
|
+
|
|
201
|
+
# Initial save
|
|
202
|
+
text = self.clipboard.text()
|
|
203
|
+
if text:
|
|
204
|
+
db.add_item(text)
|
|
205
|
+
|
|
206
|
+
def on_clipboard_change(self):
|
|
207
|
+
text = self.clipboard.text()
|
|
208
|
+
if text:
|
|
209
|
+
# Ignore clipboard updates that were triggered by us selecting an item
|
|
210
|
+
if getattr(self.gui, 'internal_clipboard_update_text', None) == text:
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
db.add_item(text)
|
|
214
|
+
if self.gui.isVisible():
|
|
215
|
+
self.gui.load_items()
|
|
216
|
+
|
|
217
|
+
def main():
|
|
218
|
+
app = QApplication(sys.argv)
|
|
219
|
+
|
|
220
|
+
# Keep the application running even if the window is closed
|
|
221
|
+
app.setQuitOnLastWindowClosed(False)
|
|
222
|
+
|
|
223
|
+
# Initialize DB in case GUI runs before daemon
|
|
224
|
+
db.init_db()
|
|
225
|
+
|
|
226
|
+
gui = ClipboardGUI()
|
|
227
|
+
|
|
228
|
+
# Initialize Clipboard Monitor
|
|
229
|
+
# we assign it to a variable so it doesn't get garbage collected
|
|
230
|
+
monitor = ClipboardMonitor(app.clipboard(), gui)
|
|
231
|
+
|
|
232
|
+
# Setup System Tray Icon
|
|
233
|
+
# We use a standard system icon like 'edit-paste' or generic document
|
|
234
|
+
tray_icon = QSystemTrayIcon(QIcon.fromTheme('edit-paste'), app)
|
|
235
|
+
if not tray_icon.icon().isNull():
|
|
236
|
+
pass
|
|
237
|
+
else:
|
|
238
|
+
# Fallback to another standard icon if 'edit-paste' is not found
|
|
239
|
+
tray_icon = QSystemTrayIcon(QIcon.fromTheme('document-properties'), app)
|
|
240
|
+
|
|
241
|
+
tray_icon.setToolTip("Clipboard Manager")
|
|
242
|
+
|
|
243
|
+
# Create tray menu
|
|
244
|
+
menu = QMenu()
|
|
245
|
+
show_action = menu.addAction("Show History")
|
|
246
|
+
show_action.triggered.connect(gui.show)
|
|
247
|
+
quit_action = menu.addAction("Quit")
|
|
248
|
+
quit_action.triggered.connect(app.quit)
|
|
249
|
+
tray_icon.setContextMenu(menu)
|
|
250
|
+
|
|
251
|
+
# Show window when clicking the tray icon
|
|
252
|
+
tray_icon.activated.connect(
|
|
253
|
+
lambda reason: gui.show() if reason == QSystemTrayIcon.ActivationReason.Trigger else None
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
tray_icon.show()
|
|
257
|
+
|
|
258
|
+
# Center on screen
|
|
259
|
+
screen_geometry = app.primaryScreen().geometry()
|
|
260
|
+
x = (screen_geometry.width() - gui.width()) // 2
|
|
261
|
+
y = (screen_geometry.height() - gui.height()) // 2
|
|
262
|
+
gui.move(x, y)
|
|
263
|
+
|
|
264
|
+
gui.show()
|
|
265
|
+
sys.exit(app.exec())
|
|
266
|
+
|
|
267
|
+
if __name__ == '__main__':
|
|
268
|
+
main()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
DB_PATH = os.path.expanduser('~/.local/share/clipboard_history.db')
|
|
5
|
+
|
|
6
|
+
def get_connection():
|
|
7
|
+
# Ensure directory exists
|
|
8
|
+
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
|
9
|
+
return sqlite3.connect(DB_PATH)
|
|
10
|
+
|
|
11
|
+
def init_db():
|
|
12
|
+
conn = get_connection()
|
|
13
|
+
cursor = conn.cursor()
|
|
14
|
+
cursor.execute('''
|
|
15
|
+
CREATE TABLE IF NOT EXISTS clipboard_items (
|
|
16
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17
|
+
content TEXT UNIQUE NOT NULL,
|
|
18
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
19
|
+
)
|
|
20
|
+
''')
|
|
21
|
+
conn.commit()
|
|
22
|
+
conn.close()
|
|
23
|
+
|
|
24
|
+
def add_item(content: str):
|
|
25
|
+
if not content or not content.strip():
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
conn = get_connection()
|
|
29
|
+
cursor = conn.cursor()
|
|
30
|
+
# Insert or update timestamp if it already exists to bring it to top
|
|
31
|
+
cursor.execute('''
|
|
32
|
+
INSERT INTO clipboard_items (content, timestamp)
|
|
33
|
+
VALUES (?, CURRENT_TIMESTAMP)
|
|
34
|
+
ON CONFLICT(content) DO UPDATE SET timestamp = CURRENT_TIMESTAMP
|
|
35
|
+
''', (content,))
|
|
36
|
+
conn.commit()
|
|
37
|
+
|
|
38
|
+
# Keep only the latest 100 items to prevent db from growing indefinitely
|
|
39
|
+
cursor.execute('''
|
|
40
|
+
DELETE FROM clipboard_items
|
|
41
|
+
WHERE id NOT IN (
|
|
42
|
+
SELECT id FROM clipboard_items
|
|
43
|
+
ORDER BY timestamp DESC
|
|
44
|
+
LIMIT 100
|
|
45
|
+
)
|
|
46
|
+
''')
|
|
47
|
+
conn.commit()
|
|
48
|
+
conn.close()
|
|
49
|
+
|
|
50
|
+
def get_items(limit=50):
|
|
51
|
+
conn = get_connection()
|
|
52
|
+
cursor = conn.cursor()
|
|
53
|
+
cursor.execute('''
|
|
54
|
+
SELECT id, content FROM clipboard_items
|
|
55
|
+
ORDER BY timestamp DESC
|
|
56
|
+
LIMIT ?
|
|
57
|
+
''', (limit,))
|
|
58
|
+
items = cursor.fetchall()
|
|
59
|
+
conn.close()
|
|
60
|
+
return items
|
|
61
|
+
|
|
62
|
+
def delete_item(item_id: int):
|
|
63
|
+
conn = get_connection()
|
|
64
|
+
cursor = conn.cursor()
|
|
65
|
+
cursor.execute('DELETE FROM clipboard_items WHERE id = ?', (item_id,))
|
|
66
|
+
conn.commit()
|
|
67
|
+
conn.close()
|
|
68
|
+
|
|
69
|
+
if __name__ == '__main__':
|
|
70
|
+
init_db()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linux-copycache
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: A lightweight, fast, and reliable clipboard manager for Linux (Ubuntu) mimicking Win+V.
|
|
5
|
+
Home-page: https://github.com/randhana/linux-clipboard-manager
|
|
6
|
+
Author: Pulathisi Kariyawasam
|
|
7
|
+
Author-email: pulathisi.kariyawasam@gmail.com
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/randhana/linux-clipboard-manager/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: PyQt6>=6.4.0
|
|
16
|
+
Requires-Dist: pyperclip>=1.8.2
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: project-url
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# Linux Clipboard Manager
|
|
29
|
+
|
|
30
|
+
A lightweight, local-only clipboard manager for Linux built with Python and PyQt6.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
- Background daemon to monitor clipboard history.
|
|
34
|
+
- Popup GUI to browse and search previous items.
|
|
35
|
+
- Auto-paste integration with `xdotool` (X11) or `wtype` (Wayland).
|
|
36
|
+
|
|
37
|
+
## Prerequisites
|
|
38
|
+
- **Python 3**
|
|
39
|
+
- **xdotool** (for X11 users) or **wtype** (for Wayland users)
|
|
40
|
+
|
|
41
|
+
You can install `wtype` or `xdotool` on Ubuntu using:
|
|
42
|
+
```bash
|
|
43
|
+
sudo apt install xdotool wtype
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
You can install the Clipboard Manager easily via `pip`:
|
|
48
|
+
```bash
|
|
49
|
+
pip install linux-clipboard-manager
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### System Dependencies
|
|
53
|
+
The auto-pasting functionality requires `xdotool` and `xclip` to be installed on your system.
|
|
54
|
+
```bash
|
|
55
|
+
sudo apt install xdotool xclip
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Running the tool
|
|
59
|
+
Once installed, you can start the application from anywhere in your terminal:
|
|
60
|
+
```bash
|
|
61
|
+
linux-clipboard
|
|
62
|
+
```
|
|
63
|
+
The application will launch, dock itself to your system tray, and quietly monitor your copied text!
|
|
64
|
+
|
|
65
|
+
## Setting up the `Super+V` (Win+V) Shortcut in Ubuntu
|
|
66
|
+
For the native Windows experience, set up a custom shortcut:
|
|
67
|
+
1. Open **Settings** -> **Keyboard**.
|
|
68
|
+
2. Scroll to the bottom and click **View and Customize Shortcuts**.
|
|
69
|
+
3. Select **Custom Shortcuts** and click the **+** or **Add Shortcut** button.
|
|
70
|
+
4. **Name:** `Clipboard Manager`
|
|
71
|
+
5. **Command:** `linux-clipboard`
|
|
72
|
+
6. **Shortcut:** Press `Super + V` (Windows key + V).
|
|
73
|
+
7. Click **Add**.
|
|
74
|
+
|
|
75
|
+
Now, whenever you press `Super + V`, the history window will pop up!
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
src/linux_clipboard_manager/__init__.py
|
|
4
|
+
src/linux_clipboard_manager/clipboard_daemon.py
|
|
5
|
+
src/linux_clipboard_manager/clipboard_gui.py
|
|
6
|
+
src/linux_clipboard_manager/db.py
|
|
7
|
+
src/linux_copycache.egg-info/PKG-INFO
|
|
8
|
+
src/linux_copycache.egg-info/SOURCES.txt
|
|
9
|
+
src/linux_copycache.egg-info/dependency_links.txt
|
|
10
|
+
src/linux_copycache.egg-info/entry_points.txt
|
|
11
|
+
src/linux_copycache.egg-info/requires.txt
|
|
12
|
+
src/linux_copycache.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
linux_clipboard_manager
|