Repository navigation
Expand file tree
/
Copy pathsender.py
More file actions
147 lines (123 loc) · 5.23 KB
/
Copy pathsender.py
File metadata and controls
147 lines (123 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import loguru, time, threading, pyclip, platform, subprocess
from concurrent.futures import ThreadPoolExecutor
from cryptography.fernet import Fernet
from websockets.sync.client import connect
class Sender:
_CONNECT_CONFIG = {
'open_timeout': 5,
'close_timeout': 3,
'max_size': 10 * 1024 * 1024,
'max_queue': 32,
'ping_interval': 30,
'ping_timeout': 40,
}
def __init__(self, peers, key):
self.peers = peers
self.connections = {}
self.cipher = Fernet(key)
self.last_message = None
self._running = False
self._clipboard_lock = threading.Lock()
self._executor = ThreadPoolExecutor(max_workers=1)
def ensure_connections(self):
while self._running:
for peer_name, peer_url in self.peers.items():
try:
if peer_name not in self.connections or not self.is_ws_connected(self.connections[peer_name]):
if peer_name in self.connections:
try:
self.connections[peer_name].close()
except Exception as close_err:
loguru.logger.debug(f'Close connection error ({peer_name}): {close_err}')
finally:
del self.connections[peer_name]
self.connections[peer_name] = connect(peer_url, **self._CONNECT_CONFIG)
loguru.logger.info(f'Connection established: {peer_name}')
except Exception as e:
loguru.logger.error(f'Connection error ({peer_name}): {e}')
self.connections.pop(peer_name, None)
time.sleep(30)
def run(self):
self._running = True
loguru.logger.info(f'Sender initialized with {len(self.peers)} peer(s)')
threading.Thread(target=self.ensure_connections, daemon=True).start()
while self._running:
try:
message = self.paste()
if message is not None and message != self.last_message:
self.last_message = message
self.broadcast(message)
except Exception as e:
loguru.logger.error(f'Broadcast error: {e}')
time.sleep(3)
def broadcast(self, message):
if not self.connections:
loguru.logger.debug('No active connections available')
return
for peer_name, ws in self.connections.items():
try:
ws.send(message)
except Exception as e:
loguru.logger.error(f'Send error ({peer_name}): {e}')
def paste(self):
try:
if platform.system() == "Linux":
message = self._paste_linux_safe()
else:
message = pyclip.paste()
if message is None:
return None
prepared_message = message.decode('utf-8', errors='ignore') \
if isinstance(message, bytes) else message
if not prepared_message.strip():
return None
msg_max_size = self._CONNECT_CONFIG['max_size']
limit = msg_max_size if platform.system() != "Linux" else msg_max_size // 2
message_bytes = prepared_message.encode('utf-8')
if len(message_bytes) > limit:
loguru.logger.warning(f'Message size exceeds limit: {len(message_bytes)} bytes')
return None
preview = prepared_message[:50] + "..." if len(prepared_message) > 50 else prepared_message
loguru.logger.debug(f'Clipboard content: {preview}')
return self.cipher.encrypt(message_bytes)
except (AttributeError, UnicodeDecodeError, OSError) as e:
loguru.logger.error(f'Clipboard read error: {e}')
return None
def _paste_linux_safe(self):
with self._clipboard_lock:
try:
result = subprocess.run(
['xclip', '-selection', 'clipboard', '-o'],
capture_output=True,
timeout=1
)
if result.returncode == 0 and result.stdout:
return result.stdout
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
future = self._executor.submit(pyclip.paste)
try:
return future.result(timeout=2)
except TimeoutError:
loguru.logger.debug('Clipboard read timeout (Linux)')
return None
def stop(self):
loguru.logger.info('Sender shutdown initiated')
self._running = False
self._executor.shutdown(wait=True)
self.close()
def close(self):
for peer_name, ws in self.connections.items():
try:
if self.is_ws_connected(ws):
loguru.logger.info(f'Closing connection: {peer_name}')
ws.close()
except Exception as e:
loguru.logger.error(f'Connection close error ({peer_name}): {e}')
@staticmethod
def is_ws_connected(ws):
try:
ws.ping()
return ws.connected
except:
return False