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.
Files changed (53) hide show
  1. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/PKG-INFO +1 -1
  2. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/__init__.py +1 -1
  3. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/client.py +4 -1
  4. remotedesktop-0.9.0/src/remotedesktop/icon.py +69 -0
  5. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/server.py +4 -1
  6. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_client_window.py +1 -0
  7. remotedesktop-0.9.0/tests/test_icon.py +19 -0
  8. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_server_window.py +1 -0
  9. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/.github/workflows/ci.yml +0 -0
  10. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/.gitignore +0 -0
  11. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/CLAUDE.md +0 -0
  12. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/LICENSE +0 -0
  13. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/pyproject.toml +0 -0
  14. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/readme.md +0 -0
  15. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/run_claude.bat +0 -0
  16. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/run_client.bat +0 -0
  17. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/run_server.bat +0 -0
  18. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/scripts/make_coverage_badge.py +0 -0
  19. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/scripts/make_venv.bat +0 -0
  20. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/autostart.py +0 -0
  21. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/clipboard.py +0 -0
  22. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/config.py +0 -0
  23. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/db.py +0 -0
  24. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/discovery.py +0 -0
  25. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/frames.py +0 -0
  26. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/input_injection.py +0 -0
  27. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/inventory.py +0 -0
  28. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/logs.py +0 -0
  29. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/performance.py +0 -0
  30. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/preferences.py +0 -0
  31. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/protocol.py +0 -0
  32. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/sharing.py +0 -0
  33. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/tls.py +0 -0
  34. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/viewer.py +0 -0
  35. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/src/remotedesktop/window_state.py +0 -0
  36. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/conftest.py +0 -0
  37. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_autostart.py +0 -0
  38. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_clipboard.py +0 -0
  39. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_config.py +0 -0
  40. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_discovery.py +0 -0
  41. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_frames.py +0 -0
  42. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_input.py +0 -0
  43. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_input_injection.py +0 -0
  44. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_inventory.py +0 -0
  45. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_logs.py +0 -0
  46. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_performance.py +0 -0
  47. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_preferences.py +0 -0
  48. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_protocol.py +0 -0
  49. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_sharing.py +0 -0
  50. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_smoke.py +0 -0
  51. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_tls.py +0 -0
  52. {remotedesktop-0.8.0 → remotedesktop-0.9.0}/tests/test_window_state.py +0 -0
  53. {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.8.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
@@ -1,3 +1,3 @@
1
1
  """Remote desktop client/server for Windows computers on the same LAN."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.9.0"
@@ -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