remotedesktop 0.2.0__py3-none-any.whl → 0.4.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.
- remotedesktop/__init__.py +1 -1
- remotedesktop/autostart.py +63 -0
- remotedesktop/server.py +189 -174
- {remotedesktop-0.2.0.dist-info → remotedesktop-0.4.0.dist-info}/METADATA +15 -1
- {remotedesktop-0.2.0.dist-info → remotedesktop-0.4.0.dist-info}/RECORD +8 -7
- {remotedesktop-0.2.0.dist-info → remotedesktop-0.4.0.dist-info}/WHEEL +0 -0
- {remotedesktop-0.2.0.dist-info → remotedesktop-0.4.0.dist-info}/entry_points.txt +0 -0
- {remotedesktop-0.2.0.dist-info → remotedesktop-0.4.0.dist-info}/licenses/LICENSE +0 -0
remotedesktop/__init__.py
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Start the server automatically when the user logs in to Windows.
|
|
2
|
+
|
|
3
|
+
Uses the per-user Run registry key (HKCU), so no administrator rights are
|
|
4
|
+
needed and the server starts in the interactive session — which it needs,
|
|
5
|
+
because approving a new client is a GUI prompt. On non-Windows platforms
|
|
6
|
+
this is an inert stub, like input injection.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
_IS_WINDOWS = sys.platform == "win32"
|
|
13
|
+
if _IS_WINDOWS:
|
|
14
|
+
import winreg
|
|
15
|
+
|
|
16
|
+
_RUN_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"
|
|
17
|
+
_VALUE_NAME = "remotedesktop-server"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def server_command() -> str:
|
|
21
|
+
"""The command line that launches this installation's server."""
|
|
22
|
+
exe = Path(sys.executable).with_name("remotedesktop-server.exe")
|
|
23
|
+
if exe.exists():
|
|
24
|
+
return f'"{exe}"'
|
|
25
|
+
# Fallback (e.g. running from source without the entry-point exe).
|
|
26
|
+
return f'"{sys.executable}" -m remotedesktop.server'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Autostart:
|
|
30
|
+
"""Reads and writes the login-autostart registration.
|
|
31
|
+
|
|
32
|
+
Tests pass their own `key_path` so they never touch the real Run key.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, *, key_path: str = _RUN_KEY, value_name: str = _VALUE_NAME) -> None:
|
|
36
|
+
self.available = _IS_WINDOWS
|
|
37
|
+
self._key_path = key_path
|
|
38
|
+
self._value_name = value_name
|
|
39
|
+
|
|
40
|
+
def is_enabled(self) -> bool:
|
|
41
|
+
if not self.available:
|
|
42
|
+
return False
|
|
43
|
+
try:
|
|
44
|
+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, self._key_path) as key:
|
|
45
|
+
winreg.QueryValueEx(key, self._value_name)
|
|
46
|
+
return True
|
|
47
|
+
except OSError:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
def set_enabled(self, enabled: bool) -> None:
|
|
51
|
+
if not self.available:
|
|
52
|
+
return
|
|
53
|
+
if enabled:
|
|
54
|
+
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, self._key_path) as key:
|
|
55
|
+
winreg.SetValueEx(key, self._value_name, 0, winreg.REG_SZ, server_command())
|
|
56
|
+
else:
|
|
57
|
+
try:
|
|
58
|
+
with winreg.OpenKey(
|
|
59
|
+
winreg.HKEY_CURRENT_USER, self._key_path, 0, winreg.KEY_SET_VALUE
|
|
60
|
+
) as key:
|
|
61
|
+
winreg.DeleteValue(key, self._value_name)
|
|
62
|
+
except OSError:
|
|
63
|
+
pass # already not registered
|
remotedesktop/server.py
CHANGED
|
@@ -1,174 +1,189 @@
|
|
|
1
|
-
"""Server GUI application: shares this computer's desktop with permitted
|
|
2
|
-
clients and prompts the user to approve first-time connections."""
|
|
3
|
-
|
|
4
|
-
import socket
|
|
5
|
-
import sqlite3
|
|
6
|
-
import sys
|
|
7
|
-
import time
|
|
8
|
-
|
|
9
|
-
from PySide6.QtCore import Qt
|
|
10
|
-
from PySide6.QtGui import QCloseEvent
|
|
11
|
-
from PySide6.QtWidgets import (
|
|
12
|
-
QApplication,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
from remotedesktop
|
|
24
|
-
from remotedesktop.
|
|
25
|
-
from remotedesktop.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.
|
|
49
|
-
self.
|
|
50
|
-
|
|
51
|
-
self.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
self.
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
self.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
self.
|
|
85
|
-
self.share_server
|
|
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
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"
|
|
144
|
-
|
|
145
|
-
"
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
1
|
+
"""Server GUI application: shares this computer's desktop with permitted
|
|
2
|
+
clients and prompts the user to approve first-time connections."""
|
|
3
|
+
|
|
4
|
+
import socket
|
|
5
|
+
import sqlite3
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from PySide6.QtCore import Qt
|
|
10
|
+
from PySide6.QtGui import QCloseEvent
|
|
11
|
+
from PySide6.QtWidgets import (
|
|
12
|
+
QApplication,
|
|
13
|
+
QCheckBox,
|
|
14
|
+
QLabel,
|
|
15
|
+
QMainWindow,
|
|
16
|
+
QMessageBox,
|
|
17
|
+
QPlainTextEdit,
|
|
18
|
+
QTabWidget,
|
|
19
|
+
QVBoxLayout,
|
|
20
|
+
QWidget,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from remotedesktop import db, tls, window_state
|
|
24
|
+
from remotedesktop.autostart import Autostart
|
|
25
|
+
from remotedesktop.clipboard import ClipboardSync
|
|
26
|
+
from remotedesktop.config import PairedClients, Settings, default_config_dir, default_db_path
|
|
27
|
+
from remotedesktop.discovery import (
|
|
28
|
+
DEFAULT_CONNECT_PORT,
|
|
29
|
+
DISCOVERY_PORT,
|
|
30
|
+
DiscoveryResponder,
|
|
31
|
+
)
|
|
32
|
+
from remotedesktop.inventory import ConnectionInventory, InventoryTab
|
|
33
|
+
from remotedesktop.sharing import ShareServer
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ServerWindow(QMainWindow):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
discovery_port: int = DISCOVERY_PORT,
|
|
41
|
+
connect_port: int = DEFAULT_CONNECT_PORT,
|
|
42
|
+
paired: PairedClients | None = None,
|
|
43
|
+
credentials=None,
|
|
44
|
+
connection: sqlite3.Connection | None = None,
|
|
45
|
+
autostart: Autostart | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
super().__init__()
|
|
48
|
+
self.setWindowTitle("Remote Desktop Server")
|
|
49
|
+
self._name = socket.gethostname()
|
|
50
|
+
|
|
51
|
+
self._summary = QLabel(alignment=Qt.AlignmentFlag.AlignCenter)
|
|
52
|
+
self._autostart = autostart if autostart is not None else Autostart()
|
|
53
|
+
self.autostart_checkbox = QCheckBox("Start this server when I log in to Windows")
|
|
54
|
+
self.autostart_checkbox.setChecked(self._autostart.is_enabled())
|
|
55
|
+
self.autostart_checkbox.setEnabled(self._autostart.available)
|
|
56
|
+
self.autostart_checkbox.toggled.connect(self._on_autostart_toggled)
|
|
57
|
+
self.connection_log = QPlainTextEdit()
|
|
58
|
+
self.connection_log.setReadOnly(True)
|
|
59
|
+
self.connection_log.setMaximumBlockCount(1000)
|
|
60
|
+
status_tab = QWidget()
|
|
61
|
+
status_layout = QVBoxLayout(status_tab)
|
|
62
|
+
status_layout.addWidget(self._summary)
|
|
63
|
+
status_layout.addWidget(self.autostart_checkbox, alignment=Qt.AlignmentFlag.AlignHCenter)
|
|
64
|
+
status_layout.addWidget(self.connection_log, stretch=1)
|
|
65
|
+
|
|
66
|
+
# Tests inject a connection to a temp database; the app uses the default.
|
|
67
|
+
self._db = connection if connection is not None else db.connect(default_db_path())
|
|
68
|
+
self.inventory = ConnectionInventory(self._db, "server_peers", self)
|
|
69
|
+
tabs = QTabWidget()
|
|
70
|
+
tabs.addTab(status_tab, "Status")
|
|
71
|
+
tabs.addTab(
|
|
72
|
+
InventoryTab(self.inventory, "Revoke access", self._revoke_client),
|
|
73
|
+
"Clients on LAN",
|
|
74
|
+
)
|
|
75
|
+
self.setCentralWidget(tabs)
|
|
76
|
+
|
|
77
|
+
if credentials is None:
|
|
78
|
+
config_dir = default_config_dir()
|
|
79
|
+
credentials = tls.load_or_create_credentials(
|
|
80
|
+
config_dir / "server_cert.pem", config_dir / "server_key.pem"
|
|
81
|
+
)
|
|
82
|
+
if paired is None:
|
|
83
|
+
paired = PairedClients(self._db)
|
|
84
|
+
self._clipboard = ClipboardSync(parent=self)
|
|
85
|
+
self.share_server = ShareServer(
|
|
86
|
+
self._ask_approval,
|
|
87
|
+
credentials=credentials,
|
|
88
|
+
paired=paired,
|
|
89
|
+
clipboard=self._clipboard,
|
|
90
|
+
parent=self,
|
|
91
|
+
)
|
|
92
|
+
self.share_server.status.connect(self.log)
|
|
93
|
+
self.share_server.clientCountChanged.connect(self._update_summary)
|
|
94
|
+
self.share_server.peerEvent.connect(self._record_peer)
|
|
95
|
+
self._listening = self.share_server.listen(connect_port)
|
|
96
|
+
|
|
97
|
+
self.responder: DiscoveryResponder | None = None
|
|
98
|
+
self._discoverable = False
|
|
99
|
+
if self._listening:
|
|
100
|
+
responder = DiscoveryResponder(
|
|
101
|
+
self._name, self.share_server.port, discovery_port=discovery_port
|
|
102
|
+
)
|
|
103
|
+
try:
|
|
104
|
+
responder.start()
|
|
105
|
+
except OSError as error:
|
|
106
|
+
self.log(
|
|
107
|
+
f"Discovery unavailable (UDP port {discovery_port}): {error} — "
|
|
108
|
+
"another server may already be running"
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
self.responder = responder
|
|
112
|
+
self._discoverable = True
|
|
113
|
+
self.log(
|
|
114
|
+
f'Discoverable as "{self._name}" '
|
|
115
|
+
f"(UDP port {discovery_port}, TCP port {self.share_server.port})"
|
|
116
|
+
)
|
|
117
|
+
self._update_summary(0)
|
|
118
|
+
self._settings = Settings(self._db)
|
|
119
|
+
window_state.restore_geometry(self, self._settings, window_state.SERVER_GEOMETRY_KEY)
|
|
120
|
+
|
|
121
|
+
def log(self, message: str) -> None:
|
|
122
|
+
self.connection_log.appendPlainText(f"{time.strftime('%H:%M:%S')} {message}")
|
|
123
|
+
|
|
124
|
+
def _record_peer(self, event: dict) -> None:
|
|
125
|
+
self.inventory.record(
|
|
126
|
+
event["key"],
|
|
127
|
+
event["event"],
|
|
128
|
+
name=event.get("name", ""),
|
|
129
|
+
address=event.get("address", ""),
|
|
130
|
+
detail=event.get("detail", ""),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _update_summary(self, client_count: int) -> None:
|
|
134
|
+
if not self._listening:
|
|
135
|
+
self._summary.setText("Cannot share: the connection port is already in use")
|
|
136
|
+
return
|
|
137
|
+
discoverable = (
|
|
138
|
+
f'Discoverable on this LAN as "{self._name}"'
|
|
139
|
+
if self._discoverable
|
|
140
|
+
else "Not discoverable (discovery port in use)"
|
|
141
|
+
)
|
|
142
|
+
sharing = (
|
|
143
|
+
f"Sharing this desktop with {client_count} viewer(s)"
|
|
144
|
+
if client_count
|
|
145
|
+
else "Not sharing"
|
|
146
|
+
)
|
|
147
|
+
self._summary.setText(f"{discoverable}\n{sharing}")
|
|
148
|
+
|
|
149
|
+
def _on_autostart_toggled(self, checked: bool) -> None:
|
|
150
|
+
self._autostart.set_enabled(checked)
|
|
151
|
+
self.log(
|
|
152
|
+
"Server will start at login" if checked else "Server will no longer start at login"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def _revoke_client(self, client_id: str) -> None:
|
|
156
|
+
answer = QMessageBox.question(
|
|
157
|
+
self,
|
|
158
|
+
"Revoke access",
|
|
159
|
+
f"Revoke access for client {client_id}?\n\n"
|
|
160
|
+
"It will be disconnected now and must be approved again to reconnect.",
|
|
161
|
+
)
|
|
162
|
+
if answer == QMessageBox.StandardButton.Yes:
|
|
163
|
+
self.share_server.revoke_client(client_id)
|
|
164
|
+
|
|
165
|
+
def _ask_approval(self, client_id: str, client_name: str) -> bool:
|
|
166
|
+
answer = QMessageBox.question(
|
|
167
|
+
self,
|
|
168
|
+
"Connection request",
|
|
169
|
+
f'Allow "{client_name}" to view this desktop?\n\nClient id: {client_id}',
|
|
170
|
+
)
|
|
171
|
+
return answer == QMessageBox.StandardButton.Yes
|
|
172
|
+
|
|
173
|
+
def closeEvent(self, event: QCloseEvent) -> None:
|
|
174
|
+
window_state.save_geometry(self, self._settings, window_state.SERVER_GEOMETRY_KEY)
|
|
175
|
+
if self.responder is not None:
|
|
176
|
+
self.responder.stop()
|
|
177
|
+
self.share_server.close()
|
|
178
|
+
super().closeEvent(event)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def main() -> None: # pragma: no cover - runs the Qt event loop
|
|
182
|
+
app = QApplication(sys.argv)
|
|
183
|
+
window = ServerWindow()
|
|
184
|
+
window.show()
|
|
185
|
+
raise SystemExit(app.exec())
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__": # pragma: no cover
|
|
189
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remotedesktop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -28,6 +28,10 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
A Python client/server application that provides remote desktop for Windows
|
|
29
29
|
computers on the same LAN, with autodiscovery of servers.
|
|
30
30
|
|
|
31
|
+
The server can optionally start automatically when you log in to Windows —
|
|
32
|
+
a checkbox on its Status tab registers it under the per-user Run key (no
|
|
33
|
+
administrator rights needed).
|
|
34
|
+
|
|
31
35
|
Connections are made to the desktop screen, keyboard, mouse, and clipboard.
|
|
32
36
|
Other connections are not provided, such as shared drives, devices, or
|
|
33
37
|
multimedia (e.g., audio).
|
|
@@ -111,3 +115,13 @@ uv run pytest # run the tests
|
|
|
111
115
|
Run the tests from PowerShell or cmd, not Git Bash: Git Bash puts Git's
|
|
112
116
|
MinGW OpenSSL DLLs on `PATH`, which Qt's TLS backend loads and crashes on.
|
|
113
117
|
From PowerShell, Qt uses the Windows schannel backend as intended.
|
|
118
|
+
|
|
119
|
+
### The `badges` branch
|
|
120
|
+
|
|
121
|
+
The coverage badge above is served from the `badges` branch
|
|
122
|
+
(`raw.githubusercontent.com/.../badges/coverage.svg`). CI regenerates the
|
|
123
|
+
SVG after each test run on `master` and force-pushes it there as a single
|
|
124
|
+
orphan commit. It lives on its own branch because `master` only accepts
|
|
125
|
+
pull requests (a repository ruleset), so CI cannot commit to it directly;
|
|
126
|
+
keeping the badge in the repo avoids depending on an external coverage
|
|
127
|
+
service. The branch is generated output — never branch from it or merge it.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
remotedesktop/__init__.py,sha256=
|
|
1
|
+
remotedesktop/__init__.py,sha256=d_RMAscIXOFiew0nH6TDft-NXuqIaPI46IniyCQ20tY,100
|
|
2
|
+
remotedesktop/autostart.py,sha256=MM7qsO6MVcK4dEofjSyRPrKpBinKU5w6xaWptLDC-7g,2251
|
|
2
3
|
remotedesktop/client.py,sha256=XRGPCCmbLDTjsB6eLi8sM87pR9iXWcNJIFZmh8MlMnE,10209
|
|
3
4
|
remotedesktop/clipboard.py,sha256=aDVwBVztSh2MxgTFIMaVsoXjZIrjDeVBnSOCmou9L9A,3743
|
|
4
5
|
remotedesktop/config.py,sha256=pE8hUEnktgthRDALyHz8qdUeUKjIhcSotffgniZLszM,4285
|
|
@@ -7,13 +8,13 @@ remotedesktop/discovery.py,sha256=ai_ic2XtSKO_m8y5IFkHQ88PBDgd_N6YOEccaIBtmis,47
|
|
|
7
8
|
remotedesktop/input_injection.py,sha256=p-MRpnS7WDt5S8JK4gNC5cgAjM9DMqV-kzqJkLdtKTw,4581
|
|
8
9
|
remotedesktop/inventory.py,sha256=fJLh2Ni2oCuUTEvHNANzpVf4abJT1eLeDU-YRSHwpjQ,7406
|
|
9
10
|
remotedesktop/protocol.py,sha256=IeX-SVz2KiZzYZ3ZiAHE8ZNYC8zyVNQi5uTxcGD4Zzc,2532
|
|
10
|
-
remotedesktop/server.py,sha256=
|
|
11
|
+
remotedesktop/server.py,sha256=EgfyHEF4LYe7GjK5TZTWbB3Ul4TvH08x8MSkG_Fi3Yk,7261
|
|
11
12
|
remotedesktop/sharing.py,sha256=XgSCiBcb-r_i2RmPut3ifGS6YvE4AX9O5hr50ob2sXk,24313
|
|
12
13
|
remotedesktop/tls.py,sha256=AGrBpDq1gdjQm7TZ74y2W5-RxyEcbsFDPlNdRvadaAk,3934
|
|
13
14
|
remotedesktop/viewer.py,sha256=b0MGSVuhQKdrQM2cjxHstt_2Flgw6dZv1awAenL4Bys,6554
|
|
14
15
|
remotedesktop/window_state.py,sha256=X47Bl_FrV5WMyVDA-PWShum9pt6TMrR0HhADuz-CqNw,1012
|
|
15
|
-
remotedesktop-0.
|
|
16
|
-
remotedesktop-0.
|
|
17
|
-
remotedesktop-0.
|
|
18
|
-
remotedesktop-0.
|
|
19
|
-
remotedesktop-0.
|
|
16
|
+
remotedesktop-0.4.0.dist-info/METADATA,sha256=eXN_CFFwXAbg0J9jXzM3TDUnJWVOMAFSRfKtJ8WOaCU,5734
|
|
17
|
+
remotedesktop-0.4.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
18
|
+
remotedesktop-0.4.0.dist-info/entry_points.txt,sha256=MmHDIIsykHkOwLf_IbGweGDFA2BuITI8_lUXsMpihcg,112
|
|
19
|
+
remotedesktop-0.4.0.dist-info/licenses/LICENSE,sha256=6rWgMfDohGLDuJ0UFOyLphM28Nn0fuN26MczIQldMkc,1067
|
|
20
|
+
remotedesktop-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|