remotedesktop 0.8.0__tar.gz → 0.9.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.
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/PKG-INFO +1 -1
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/__init__.py +1 -1
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/client.py +4 -1
- remotedesktop-0.9.0/src/remotedesktop/icon.py +69 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/server.py +4 -1
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_client_window.py +1 -0
- remotedesktop-0.9.0/tests/test_icon.py +19 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_server_window.py +1 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/.github/workflows/ci.yml +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/.gitignore +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/CLAUDE.md +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/LICENSE +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/pyproject.toml +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/readme.md +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/run_claude.bat +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/run_client.bat +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/run_server.bat +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/scripts/make_coverage_badge.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/scripts/make_venv.bat +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/autostart.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/clipboard.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/config.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/db.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/discovery.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/frames.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/input_injection.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/inventory.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/logs.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/performance.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/preferences.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/protocol.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/sharing.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/tls.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/viewer.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/window_state.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/conftest.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_autostart.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_clipboard.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_config.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_discovery.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_frames.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_input.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_input_injection.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_inventory.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_logs.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_performance.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_preferences.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_protocol.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_sharing.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_smoke.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_tls.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_window_state.py +0 -0
- {remotedesktop-0.8.0 → remotedesktop-0.9.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remotedesktop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Remote desktop client/server for Windows computers on the same LAN, with autodiscovery. Provides screen, keyboard, mouse, and clipboard sharing without RDP or Microsoft authentication.
|
|
5
5
|
Author-email: James Abel <j@abel.co>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -23,7 +23,7 @@ from PySide6.QtWidgets import (
|
|
|
23
23
|
QWidget,
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
-
from remotedesktop import db, logs, window_state
|
|
26
|
+
from remotedesktop import db, icon, logs, window_state
|
|
27
27
|
from remotedesktop.clipboard import ClipboardSync
|
|
28
28
|
from remotedesktop.config import KnownServers, Settings, default_db_path, load_client_identity
|
|
29
29
|
from remotedesktop.discovery import DISCOVERY_PORT, ServerInfo, discover_servers
|
|
@@ -93,6 +93,7 @@ class ClientWindow(QMainWindow):
|
|
|
93
93
|
) -> None:
|
|
94
94
|
super().__init__()
|
|
95
95
|
self.setWindowTitle("Remote Desktop Client")
|
|
96
|
+
self.setWindowIcon(icon.app_icon("client"))
|
|
96
97
|
# Tests inject a connection to a temp database; the app uses the default.
|
|
97
98
|
self._db = connection if connection is not None else db.connect(default_db_path())
|
|
98
99
|
self._settings = Settings(self._db)
|
|
@@ -264,7 +265,9 @@ class ClientWindow(QMainWindow):
|
|
|
264
265
|
|
|
265
266
|
def main() -> None: # pragma: no cover - runs the Qt event loop
|
|
266
267
|
log_path = logs.init_logging("client")
|
|
268
|
+
icon.set_windows_app_id("remotedesktop.client")
|
|
267
269
|
app = QApplication(sys.argv)
|
|
270
|
+
app.setWindowIcon(icon.app_icon("client"))
|
|
268
271
|
window = ClientWindow() # auto_scan starts the first LAN scan
|
|
269
272
|
window.log(f"Detailed log: {log_path}")
|
|
270
273
|
window.show()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""The application icon, drawn in code.
|
|
2
|
+
|
|
3
|
+
A monitor with a mouse pointer on the screen — "this desktop is controlled
|
|
4
|
+
remotely". Painted with QPainter at every common icon size (no binary asset
|
|
5
|
+
to version or package), with an accent color per app so the client (blue)
|
|
6
|
+
and server (green) are distinguishable in the taskbar when both run on one
|
|
7
|
+
machine.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import ctypes
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from PySide6.QtCore import QPointF, QRectF, Qt
|
|
14
|
+
from PySide6.QtGui import QColor, QIcon, QPainter, QPixmap, QPolygonF
|
|
15
|
+
|
|
16
|
+
_SIZES = (16, 24, 32, 48, 64, 128, 256)
|
|
17
|
+
_FRAME = QColor("#37474f")
|
|
18
|
+
_ACCENTS = {"client": QColor("#1e88e5"), "server": QColor("#43a047")}
|
|
19
|
+
# A classic cursor-arrow outline on a 0..17 grid, placed on the screen.
|
|
20
|
+
_POINTER = [(0, 0), (0, 14), (4, 11), (7, 17), (9.5, 16), (6.5, 10), (11, 10)]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _pixmap(size: int, accent: QColor) -> QPixmap:
|
|
24
|
+
pixmap = QPixmap(size, size)
|
|
25
|
+
pixmap.fill(Qt.GlobalColor.transparent)
|
|
26
|
+
painter = QPainter(pixmap)
|
|
27
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
28
|
+
s = size / 64.0
|
|
29
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
30
|
+
# Monitor frame, screen, stand, base.
|
|
31
|
+
painter.setBrush(_FRAME)
|
|
32
|
+
painter.drawRoundedRect(QRectF(4 * s, 6 * s, 56 * s, 40 * s), 5 * s, 5 * s)
|
|
33
|
+
painter.setBrush(accent)
|
|
34
|
+
painter.drawRoundedRect(QRectF(9 * s, 11 * s, 46 * s, 30 * s), 2 * s, 2 * s)
|
|
35
|
+
painter.setBrush(_FRAME)
|
|
36
|
+
painter.drawRect(QRectF(28 * s, 46 * s, 8 * s, 6 * s))
|
|
37
|
+
painter.drawRoundedRect(QRectF(18 * s, 52 * s, 28 * s, 5 * s), 2 * s, 2 * s)
|
|
38
|
+
# Mouse pointer on the screen: remote control.
|
|
39
|
+
pointer = QPolygonF(
|
|
40
|
+
[QPointF((22 + x * 1.4) * s, (14 + y * 1.4) * s) for x, y in _POINTER]
|
|
41
|
+
)
|
|
42
|
+
painter.setBrush(QColor("white"))
|
|
43
|
+
painter.drawPolygon(pointer)
|
|
44
|
+
painter.end()
|
|
45
|
+
return pixmap
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def app_icon(role: str) -> QIcon:
|
|
49
|
+
"""The window/taskbar icon for "client" or "server"."""
|
|
50
|
+
accent = _ACCENTS[role]
|
|
51
|
+
icon = QIcon()
|
|
52
|
+
for size in _SIZES:
|
|
53
|
+
icon.addPixmap(_pixmap(size, accent))
|
|
54
|
+
return icon
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def set_windows_app_id(app_id: str) -> None:
|
|
58
|
+
"""Give this process its own Windows taskbar identity.
|
|
59
|
+
|
|
60
|
+
Without it, Windows groups the app under the Python interpreter's
|
|
61
|
+
identity and may show the interpreter's icon in the taskbar instead of
|
|
62
|
+
the window icon. Harmless no-op off Windows or on failure.
|
|
63
|
+
"""
|
|
64
|
+
if sys.platform != "win32": # pragma: no cover - Windows is the target
|
|
65
|
+
return
|
|
66
|
+
try:
|
|
67
|
+
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
|
|
68
|
+
except (AttributeError, OSError): # pragma: no cover - defensive
|
|
69
|
+
pass
|
|
@@ -22,7 +22,7 @@ from PySide6.QtWidgets import (
|
|
|
22
22
|
QWidget,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
from remotedesktop import db, logs, tls, window_state
|
|
25
|
+
from remotedesktop import db, icon, logs, tls, window_state
|
|
26
26
|
from remotedesktop.autostart import Autostart
|
|
27
27
|
from remotedesktop.clipboard import ClipboardSync
|
|
28
28
|
from remotedesktop.config import PairedClients, Settings, default_config_dir, default_db_path
|
|
@@ -52,6 +52,7 @@ class ServerWindow(QMainWindow):
|
|
|
52
52
|
) -> None:
|
|
53
53
|
super().__init__()
|
|
54
54
|
self.setWindowTitle("Remote Desktop Server")
|
|
55
|
+
self.setWindowIcon(icon.app_icon("server"))
|
|
55
56
|
self._name = socket.gethostname()
|
|
56
57
|
|
|
57
58
|
self._summary = QLabel(alignment=Qt.AlignmentFlag.AlignCenter)
|
|
@@ -246,7 +247,9 @@ class ServerWindow(QMainWindow):
|
|
|
246
247
|
|
|
247
248
|
def main() -> None: # pragma: no cover - runs the Qt event loop
|
|
248
249
|
log_path = logs.init_logging("server")
|
|
250
|
+
icon.set_windows_app_id("remotedesktop.server")
|
|
249
251
|
app = QApplication(sys.argv)
|
|
252
|
+
app.setWindowIcon(icon.app_icon("server"))
|
|
250
253
|
window = ServerWindow()
|
|
251
254
|
window.log(f"Detailed log: {log_path}")
|
|
252
255
|
window.show()
|
|
@@ -34,6 +34,7 @@ def test_window_starts_disconnected(qapp, tmp_path):
|
|
|
34
34
|
labels = [tabs.tabText(i) for i in range(tabs.count())]
|
|
35
35
|
assert "Performance" in labels and "Preferences" in labels
|
|
36
36
|
assert not window.performance._timer.isActive() # idle: no periodic work
|
|
37
|
+
assert not window.windowIcon().isNull()
|
|
37
38
|
window.close()
|
|
38
39
|
|
|
39
40
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from remotedesktop.icon import app_icon
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_icons_render_for_both_roles(qapp):
|
|
7
|
+
client, server = app_icon("client"), app_icon("server")
|
|
8
|
+
for icon in (client, server):
|
|
9
|
+
assert not icon.isNull()
|
|
10
|
+
for size in (16, 32, 256):
|
|
11
|
+
pixmap = icon.pixmap(size)
|
|
12
|
+
assert not pixmap.isNull()
|
|
13
|
+
# The roles are distinguishable (different accent colors).
|
|
14
|
+
assert client.pixmap(64).toImage() != server.pixmap(64).toImage()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_unknown_role_is_rejected(qapp):
|
|
18
|
+
with pytest.raises(KeyError):
|
|
19
|
+
app_icon("toaster")
|
|
@@ -39,6 +39,7 @@ def test_window_listens_and_is_discoverable(qapp, credentials, tmp_path):
|
|
|
39
39
|
labels = [tabs.tabText(i) for i in range(tabs.count())]
|
|
40
40
|
assert "Performance" in labels and "Preferences" in labels
|
|
41
41
|
assert not window.performance._timer.isActive() # idle: no periodic work
|
|
42
|
+
assert not window.windowIcon().isNull()
|
|
42
43
|
finally:
|
|
43
44
|
window.close()
|
|
44
45
|
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|