tabby-rdp 0.2.7 → 0.3.1

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 (59) hide show
  1. package/CHANGELOG.md +72 -2
  2. package/README.md +182 -21
  3. package/dist/connectionStatus.js +140 -0
  4. package/dist/connectionStatus.js.map +1 -0
  5. package/dist/desk.js +21 -10
  6. package/dist/desk.js.map +1 -1
  7. package/dist/deskScript.js +3 -0
  8. package/dist/deskScript.js.map +1 -1
  9. package/dist/desktop.service.js +994 -53
  10. package/dist/desktop.service.js.map +1 -1
  11. package/dist/desktopForm.js +64 -19
  12. package/dist/desktopForm.js.map +1 -1
  13. package/dist/desktops.js +60 -21
  14. package/dist/desktops.js.map +1 -1
  15. package/dist/fileTransfer.js +152 -5
  16. package/dist/fileTransfer.js.map +1 -1
  17. package/dist/header.js +22 -7
  18. package/dist/header.js.map +1 -1
  19. package/dist/help.js +241 -0
  20. package/dist/help.js.map +1 -0
  21. package/dist/index.js +16 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/keyboard.js +142 -5
  24. package/dist/keyboard.js.map +1 -1
  25. package/dist/microphone.js +295 -0
  26. package/dist/microphone.js.map +1 -0
  27. package/dist/osd.js +79 -0
  28. package/dist/osd.js.map +1 -0
  29. package/dist/probe.js +33 -0
  30. package/dist/probe.js.map +1 -0
  31. package/dist/rdcleanpath.js +132 -7
  32. package/dist/rdcleanpath.js.map +1 -1
  33. package/dist/rdpFile.js +47 -0
  34. package/dist/rdpFile.js.map +1 -0
  35. package/dist/rdpProfile.js +367 -0
  36. package/dist/rdpProfile.js.map +1 -0
  37. package/dist/remoteSetup.js +90 -11
  38. package/dist/remoteSetup.js.map +1 -1
  39. package/dist/settingsPage.js +554 -0
  40. package/dist/settingsPage.js.map +1 -0
  41. package/dist/signin.js +21 -2
  42. package/dist/signin.js.map +1 -1
  43. package/dist/ssh.js +28 -0
  44. package/dist/ssh.js.map +1 -1
  45. package/dist/targets.js +148 -8
  46. package/dist/targets.js.map +1 -1
  47. package/dist/ui.js +163 -24
  48. package/dist/ui.js.map +1 -1
  49. package/dist/updates.js +123 -0
  50. package/dist/updates.js.map +1 -0
  51. package/dist/vms.js +90 -0
  52. package/dist/vms.js.map +1 -0
  53. package/dist/wake.js +124 -0
  54. package/dist/wake.js.map +1 -0
  55. package/package.json +2 -1
  56. package/vendor/IRONRDP-LICENSE +3 -3
  57. package/vendor/iron-remote-desktop-rdp.js +698 -451
  58. package/vendor/iron-remote-desktop.js +87 -81
  59. package/vendor/ironrdp_web_bg.wasm +0 -0
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.RDPProfileOpener = exports.RDPCommands = exports.RDPProfilesService = exports.RDPProfileSettingsComponent = exports.RDPTabRecovery = exports.RDPTabComponent = void 0;
13
+ const core_1 = require("@angular/core");
14
+ const tabby_core_1 = require("tabby-core");
15
+ const tabby_terminal_1 = require("tabby-terminal");
16
+ const desktop_service_1 = require("./desktop.service");
17
+ const desktopForm_1 = require("./desktopForm");
18
+ const help_1 = require("./help");
19
+ const desktops_1 = require("./desktops");
20
+ const signin_1 = require("./signin");
21
+ const ssh_1 = require("./ssh");
22
+ const targets_1 = require("./targets");
23
+ /** Input of an SSH tab opened for an RDP profile with `via`: the desktop to show once SSH is connected. */
24
+ const OPEN_DESKTOP = 'trdOpenDesktop';
25
+ const RECOVERY_TYPE = 'app:rdp-tab';
26
+ // ---- the tab -----------------------------------------------------------------------------------
27
+ const TAB_STYLE = `
28
+ rdp-tab { flex: auto; display: flex; position: relative; overflow: hidden; background: #111; }
29
+ rdp-tab .trd-rdp-idle { position: absolute; inset: 0; display: flex; flex-direction: column; gap: 14px; align-items: center;
30
+ justify-content: center; padding: 2em; text-align: center; color: #bbb; font-size: 13px; white-space: pre-line; }
31
+ `;
32
+ /**
33
+ * A remote desktop tab: the desktop layer (the same one SSH tabs get) fills it, and there is no console under it.
34
+ * A component because Tabby opens profiles as tab components; it holds no UI of its own beyond a line and a Connect
35
+ * button for when no desktop is open (after Disconnect).
36
+ */
37
+ let RDPTabComponent = class RDPTabComponent extends tabby_core_1.BaseTabComponent {
38
+ constructor(injector, element, desktop, targets, zone) {
39
+ super(injector);
40
+ this.element = element;
41
+ this.desktop = desktop;
42
+ this.targets = targets;
43
+ this.zone = zone;
44
+ }
45
+ ngOnInit() {
46
+ if (!this.title) {
47
+ this.setTitle(this.profile.name);
48
+ }
49
+ this.idle = document.createElement('div');
50
+ this.idle.className = 'trd-rdp-idle';
51
+ this.idle.innerHTML = '<div class="trd-rdp-idle-text"></div><button class="btn btn-secondary">Connect</button>';
52
+ this.idle.querySelector('button').addEventListener('click', () => this.zone.run(() => this.open()));
53
+ this.element.nativeElement.appendChild(this.idle);
54
+ this.subscribeUntilDestroyed(this.desktop.changed$, () => this.update());
55
+ this.update();
56
+ // Once the tab is laid out, so that the first connection gets the pane's size.
57
+ setTimeout(() => this.open(true));
58
+ }
59
+ update() {
60
+ const o = this.profile.options;
61
+ const text = o.via
62
+ ? `${this.profile.name} goes through an SSH profile that no longer exists. Choose another in its profile settings.`
63
+ : `${this.profile.name} (${(0, desktopForm_1.formatAddress)(o.host, o.port || 3389)}) is not connected.`;
64
+ this.idle.querySelector('.trd-rdp-idle-text').textContent = text;
65
+ this.idle.querySelector('button').style.display = o.via ? 'none' : '';
66
+ this.idle.style.display = this.desktop.has(this) ? 'none' : '';
67
+ }
68
+ async open(first = false) {
69
+ if (this.profile.options.via || !this.profile.options.host) {
70
+ return;
71
+ }
72
+ await this.targets.targetOf(this);
73
+ const elsewhere = first && this.desktop.isOpenElsewhere(this);
74
+ // Opened again while another tab has it: that tab comes forward (one desktop per server), and this one goes.
75
+ await this.desktop.showDesktop(this);
76
+ if (elsewhere) {
77
+ this.destroy();
78
+ }
79
+ }
80
+ async getRecoveryToken() {
81
+ return { type: RECOVERY_TYPE, profile: this.profile };
82
+ }
83
+ };
84
+ exports.RDPTabComponent = RDPTabComponent;
85
+ exports.RDPTabComponent = RDPTabComponent = __decorate([
86
+ (0, core_1.Component)({
87
+ selector: 'rdp-tab',
88
+ template: '',
89
+ styles: [TAB_STYLE],
90
+ // The idle message is built by hand, outside the template, so its styles can't be scoped to it.
91
+ encapsulation: core_1.ViewEncapsulation.None,
92
+ }),
93
+ __metadata("design:paramtypes", [core_1.Injector,
94
+ core_1.ElementRef,
95
+ desktop_service_1.RemoteDesktopService,
96
+ targets_1.RemoteTargets,
97
+ core_1.NgZone])
98
+ ], RDPTabComponent);
99
+ /** Reopens remote desktop tabs when Tabby restores its tabs (and for Duplicate). */
100
+ let RDPTabRecovery = class RDPTabRecovery extends tabby_core_1.TabRecoveryProvider {
101
+ constructor(injector) {
102
+ super();
103
+ this.injector = injector;
104
+ }
105
+ async applicableTo(token) {
106
+ return token.type === RECOVERY_TYPE;
107
+ }
108
+ async recover(token) {
109
+ return {
110
+ type: RDPTabComponent,
111
+ inputs: { profile: this.injector.get(tabby_core_1.ProfilesService).getConfigProxyForProfile(token.profile) },
112
+ };
113
+ }
114
+ };
115
+ exports.RDPTabRecovery = RDPTabRecovery;
116
+ exports.RDPTabRecovery = RDPTabRecovery = __decorate([
117
+ (0, core_1.Injectable)(),
118
+ __metadata("design:paramtypes", [core_1.Injector])
119
+ ], RDPTabRecovery);
120
+ // ---- profile settings --------------------------------------------------------------------------
121
+ /**
122
+ * The profile's own fields in Tabby's profile editor (name, group, icon and color are Tabby's). Plain DOM like the
123
+ * rest of the plugin, writing to the profile as you type.
124
+ */
125
+ let RDPProfileSettingsComponent = class RDPProfileSettingsComponent {
126
+ constructor(element, injector) {
127
+ this.element = element;
128
+ this.injector = injector;
129
+ }
130
+ ngOnInit() {
131
+ const o = this.profile.options;
132
+ const account = () => `${o.username ?? ''}\n${o.domain ?? ''}`;
133
+ // No id for a new profile (no address yet): it has nothing saved to take along.
134
+ this.before = { id: o.host ? (0, desktops_1.desktopIdOf)(o) : '', account: account(), via: o.via ?? '' };
135
+ const root = this.element.nativeElement;
136
+ root.innerHTML = `
137
+ <div class="mb-3">
138
+ <label>Address</label>
139
+ <input class="form-control" name="address" spellcheck="false" placeholder="e.g. 192.168.1.20, or pc.local:3390">
140
+ <div class="text-danger small" data-for="address"></div>
141
+ </div>
142
+ <div class="mb-3">
143
+ <label>Connect</label>
144
+ <select class="form-control" name="via"><option value="">Directly from this computer</option></select>
145
+ <div class="text-muted small">Through an SSH profile, the address is as that host sees it, and the desktop opens over its SSH tab.</div>
146
+ </div>
147
+ <div class="mb-3">
148
+ <label>Kind</label>
149
+ <select class="form-control" name="kind">
150
+ <option value="windows">Windows (or another RDP server)</option>
151
+ <option value="xrdp">xrdp (Linux: KDE, XFCE, MATE, …)</option>
152
+ <option value="gnome">GNOME Remote Desktop (needs the graphics pipeline)</option>
153
+ </select>
154
+ </div>
155
+ <div class="mb-3">
156
+ <label>User name</label>
157
+ <input class="form-control" name="username" spellcheck="false" placeholder="Optional; asked when connecting. DOMAIN\\user works too">
158
+ </div>
159
+ <div class="mb-3">
160
+ <label>Domain</label>
161
+ <input class="form-control" name="domain" spellcheck="false" placeholder="Optional">
162
+ </div>`;
163
+ const field = (name) => root.querySelector(`[name="${name}"]`);
164
+ field('address').value = o.host ? (0, desktopForm_1.formatAddress)(o.host, o.port || 3389) : '';
165
+ field('kind').value = o.kind === 'gnome' || o.kind === 'xrdp' ? o.kind : 'windows';
166
+ field('username').value = o.username ?? '';
167
+ field('domain').value = o.domain ?? '';
168
+ field('address').addEventListener('input', () => {
169
+ const text = field('address').value;
170
+ const address = (0, desktopForm_1.parseAddress)(text);
171
+ root.querySelector('[data-for=address]').textContent = text.trim() && !address ? 'This doesn\'t look like an address.' : '';
172
+ if (address) {
173
+ o.host = address.host;
174
+ o.port = address.port;
175
+ }
176
+ });
177
+ field('kind').addEventListener('change', () => {
178
+ const kind = field('kind').value;
179
+ o.kind = kind === 'gnome' || kind === 'xrdp' ? kind : 'windows';
180
+ });
181
+ field('username').addEventListener('input', () => { o.username = field('username').value.trim(); });
182
+ field('domain').addEventListener('input', () => { o.domain = field('domain').value.trim(); });
183
+ const via = field('via');
184
+ via.addEventListener('change', () => { o.via = via.value; });
185
+ this.injector.get(tabby_core_1.ProfilesService).getProfiles().then(profiles => {
186
+ const ssh = profiles.filter(p => p.type === 'ssh' && !p.isTemplate && p.id);
187
+ if (o.via && !ssh.some(p => p.id === o.via)) {
188
+ via.append(new Option('(an SSH profile that no longer exists)', o.via));
189
+ }
190
+ via.append(...ssh.map(p => new Option(`Through ${p.name}`, p.id)));
191
+ via.value = o.via ?? '';
192
+ }, () => null);
193
+ }
194
+ save() {
195
+ const o = this.profile.options;
196
+ const { id, account, via } = this.before;
197
+ if (!id) {
198
+ return;
199
+ }
200
+ if ((o.via ?? '') !== via) {
201
+ // Reached another way, it is another desktop. A direct one's saved account and certificate were its own; one
202
+ // behind a host shares them with that host's desktop at the same address, so they stay.
203
+ if (!via) {
204
+ (0, signin_1.forgetCredentials)(`${desktops_1.DIRECT_KEY}#${id}`);
205
+ this.injector.get(desktop_service_1.RemoteDesktopService).forgetCertificatesFor(id, true);
206
+ }
207
+ return;
208
+ }
209
+ const accountChanged = `${o.username ?? ''}\n${o.domain ?? ''}` !== account;
210
+ this.injector.get(desktop_service_1.RemoteDesktopService).desktopEdited(id, (0, desktops_1.desktopIdOf)(o), accountChanged, !via).catch(() => null);
211
+ }
212
+ };
213
+ exports.RDPProfileSettingsComponent = RDPProfileSettingsComponent;
214
+ exports.RDPProfileSettingsComponent = RDPProfileSettingsComponent = __decorate([
215
+ (0, core_1.Component)({
216
+ selector: 'rdp-profile-settings',
217
+ template: '',
218
+ }),
219
+ __metadata("design:paramtypes", [core_1.ElementRef, core_1.Injector])
220
+ ], RDPProfileSettingsComponent);
221
+ // ---- the profile provider ----------------------------------------------------------------------
222
+ let RDPProfilesService = class RDPProfilesService extends tabby_core_1.QuickConnectProfileProvider {
223
+ // ProfilesService depends on this provider: it is looked up when needed, not injected.
224
+ constructor(injector, config) {
225
+ super();
226
+ this.injector = injector;
227
+ this.config = config;
228
+ this.id = targets_1.RDP_PROFILE_TYPE;
229
+ this.name = 'Remote desktop (RDP)';
230
+ this.settingsComponent = RDPProfileSettingsComponent;
231
+ this.configDefaults = {
232
+ options: { host: '', port: 3389, kind: 'windows', username: '', domain: '', via: '' },
233
+ clearServiceMessagesOnConnect: false,
234
+ };
235
+ }
236
+ async getBuiltinProfiles() {
237
+ return [{
238
+ id: `${targets_1.RDP_PROFILE_TYPE}:template`,
239
+ type: targets_1.RDP_PROFILE_TYPE,
240
+ name: 'Remote desktop (RDP)',
241
+ icon: 'fas fa-desktop',
242
+ options: { host: '', port: 3389, kind: 'windows', username: '', domain: '', via: '' },
243
+ isBuiltin: true,
244
+ isTemplate: true,
245
+ }];
246
+ }
247
+ async getNewTabParameters(profile) {
248
+ if (profile.options.via) {
249
+ // The desktop behind an SSH host opens over that host's SSH tab, like any desktop behind a host.
250
+ const profiles = this.injector.get(tabby_core_1.ProfilesService);
251
+ const ssh = (await profiles.getProfiles()).find(p => p.id === profile.options.via);
252
+ const params = ssh && await profiles.newTabParametersForProfile(ssh);
253
+ if (params) {
254
+ params.inputs = { ...params.inputs, [OPEN_DESKTOP]: (0, desktops_1.desktopIdOf)(profile.options) };
255
+ return params;
256
+ }
257
+ // The SSH profile is gone: the tab says so.
258
+ }
259
+ return { type: RDPTabComponent, inputs: { profile } };
260
+ }
261
+ getSuggestedName(profile) {
262
+ return profile.options?.host || null;
263
+ }
264
+ getDescription(profile) {
265
+ const o = profile.options ?? {};
266
+ if (!o.host) {
267
+ return '';
268
+ }
269
+ const address = (0, desktopForm_1.formatAddress)(o.host, o.port || 3389);
270
+ const via = o.via ? (this.config.store.profiles ?? []).find((p) => p.id === o.via)?.name ?? 'an SSH profile' : null;
271
+ return via ? `${address} via ${via}` : address;
272
+ }
273
+ /** `[user@]host[:port]`, typed in Tabby's profile selector. */
274
+ quickConnect(query) {
275
+ const at = query.lastIndexOf('@');
276
+ const address = (0, desktopForm_1.parseAddress)(query.slice(at + 1));
277
+ if (!address) {
278
+ return null;
279
+ }
280
+ return {
281
+ name: query.trim(),
282
+ type: targets_1.RDP_PROFILE_TYPE,
283
+ options: { host: address.host, port: address.port, username: at > 0 ? query.slice(0, at).trim() : '' },
284
+ };
285
+ }
286
+ intoQuickConnectString(profile) {
287
+ const o = profile.options;
288
+ return o.via ? null : `${o.username ? `${o.username}@` : ''}${(0, desktopForm_1.formatAddress)(o.host, o.port || 3389)}`;
289
+ }
290
+ deleteProfile(profile) {
291
+ // A direct desktop's saved account and certificate are its own; one behind a host shares them with that host's
292
+ // desktop at the same address.
293
+ if (!profile.options.via) {
294
+ const id = (0, desktops_1.desktopIdOf)(profile.options);
295
+ (0, signin_1.forgetCredentials)(`${desktops_1.DIRECT_KEY}#${id}`);
296
+ this.injector.get(desktop_service_1.RemoteDesktopService).forgetCertificatesFor(id, true);
297
+ this.config.save();
298
+ }
299
+ }
300
+ };
301
+ exports.RDPProfilesService = RDPProfilesService;
302
+ exports.RDPProfilesService = RDPProfilesService = __decorate([
303
+ (0, core_1.Injectable)({ providedIn: 'root' }),
304
+ __metadata("design:paramtypes", [core_1.Injector, tabby_core_1.ConfigService])
305
+ ], RDPProfilesService);
306
+ /** In Tabby's command palette, also where no SSH tab (and so no Remote Desktop › Settings menu) is at hand. */
307
+ let RDPCommands = class RDPCommands extends tabby_core_1.CommandProvider {
308
+ constructor(desktop, help) {
309
+ super();
310
+ this.desktop = desktop;
311
+ this.help = help;
312
+ }
313
+ async provide() {
314
+ return [{
315
+ id: 'tabby-rdp:import-rdp-file',
316
+ label: 'Remote desktop: import an .rdp file…',
317
+ run: () => this.desktop.importRdpFile(),
318
+ }, {
319
+ id: 'tabby-rdp:help',
320
+ label: 'Remote desktop: settings, keys and help',
321
+ run: async () => this.help.open(),
322
+ }];
323
+ }
324
+ };
325
+ exports.RDPCommands = RDPCommands;
326
+ exports.RDPCommands = RDPCommands = __decorate([
327
+ (0, core_1.Injectable)(),
328
+ __metadata("design:paramtypes", [desktop_service_1.RemoteDesktopService, help_1.RemoteDesktopHelp])
329
+ ], RDPCommands);
330
+ /** Shows the desktop an RDP profile with `via` asked for, over the SSH tab opened for it, once SSH is connected. */
331
+ let RDPProfileOpener = class RDPProfileOpener extends tabby_terminal_1.TerminalDecorator {
332
+ constructor(desktop, zone) {
333
+ super();
334
+ this.desktop = desktop;
335
+ this.zone = zone;
336
+ }
337
+ attach(terminal) {
338
+ const tab = terminal;
339
+ const id = tab[OPEN_DESKTOP];
340
+ if (!id || !(0, ssh_1.isSSHTab)(tab)) {
341
+ return;
342
+ }
343
+ // Only for the first connection, not after a reconnect.
344
+ delete tab[OPEN_DESKTOP];
345
+ let closed = false;
346
+ tab.destroyed$.subscribe(() => { closed = true; });
347
+ // Not before: the desktop layer would cover a password prompt or host key question in the terminal.
348
+ const wait = (tries) => {
349
+ if (closed) {
350
+ return;
351
+ }
352
+ if ((0, ssh_1.isConnected)(tab)) {
353
+ this.zone.run(() => this.desktop.showDesktop(tab, id));
354
+ }
355
+ else if (tries > 0) {
356
+ setTimeout(() => wait(tries - 1), 250);
357
+ }
358
+ };
359
+ this.zone.runOutsideAngular(() => wait(4 * 60 * 5));
360
+ }
361
+ };
362
+ exports.RDPProfileOpener = RDPProfileOpener;
363
+ exports.RDPProfileOpener = RDPProfileOpener = __decorate([
364
+ (0, core_1.Injectable)(),
365
+ __metadata("design:paramtypes", [desktop_service_1.RemoteDesktopService, core_1.NgZone])
366
+ ], RDPProfileOpener);
367
+ //# sourceMappingURL=rdpProfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rdpProfile.js","sourceRoot":"","sources":["../src/rdpProfile.ts"],"names":[],"mappings":"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"}
@@ -3,15 +3,51 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareRemoteDesktop = prepareRemoteDesktop;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
- // Runs as the SSH user; needs no root. Prints exactly one `RD_OK port=N user=U pass=P` or `RD_ERR <reason>` line.
6
+ const rdcleanpath_1 = require("./rdcleanpath");
7
+ // Runs as the SSH user; needs no root. Prints exactly one `RD_OK port=N user=U pass=P cert=SHA256`,
8
+ // `RD_XRDP port=N user=U` (no GNOME, but xrdp), `RD_WINDOWS` (a Windows host's POSIX sh) or `RD_ERR <reason>` line;
9
+ // with RD_OK, also an RD_XRDP line when xrdp runs besides GNOME.
7
10
  // The RDP password is generated once per remote user and kept in a 0600 file; grd reads credentials only
8
11
  // at startup, so grd is restarted only when its configuration actually changes (that drops live sessions).
9
12
  // (grdctl takes the credentials as arguments, so they are briefly visible in the remote's process list.)
10
13
  const SETUP_SCRIPT = String.raw `
11
14
  say () { printf '%s\n' "$*"; }
12
15
  fail () { say "RD_ERR $*"; exit 0; }
13
- command -v grdctl >/dev/null 2>&1 || fail "GNOME Remote Desktop (grdctl) is not installed"
14
- command -v gnome-shell >/dev/null 2>&1 || fail "gnome-shell is not installed"
16
+ # A Windows host whose PATH has a POSIX sh (Git, MSYS2, Cygwin): its desktop is Windows' own RDP server.
17
+ case "$(uname -s 2>/dev/null)" in CYGWIN*|MINGW*|MSYS*) say "RD_WINDOWS"; exit 0 ;; esac
18
+ # xrdp serves the Linux desktops other than GNOME (KDE, XFCE, MATE, …). Its port is [Globals] port= in xrdp.ini
19
+ # (default 3389): a number, or a listener such as tcp://:3390 (the first one, when there are several). Only used when
20
+ # it runs and listens, so the host's own desktop isn't one that can't answer.
21
+ XRDP_PORT= XRDP_WHY=
22
+ if [ -f /etc/xrdp/xrdp.ini ]; then
23
+ P=$(sed -n '/^\[Globals\]/,/^\[/s/^[[:space:]]*port[[:space:]]*=[[:space:]]*//p' /etc/xrdp/xrdp.ini 2>/dev/null | tr -d '\r' | head -1)
24
+ P=${"$"}{P%%[[:space:]]*}
25
+ case $P in
26
+ '') P=3389 ;;
27
+ vsock://*) P= ;;
28
+ *:*) P=${"$"}{P##*:} ;;
29
+ esac
30
+ case $P in *[!0-9]*) P= ;; esac
31
+ if ! systemctl is-active -q xrdp 2>/dev/null && ! pgrep -x xrdp >/dev/null 2>&1; then
32
+ XRDP_WHY="xrdp is installed but not running (start it: sudo systemctl enable --now xrdp)"
33
+ elif [ -z "$P" ]; then
34
+ XRDP_WHY="xrdp does not listen on a TCP port (see port= in /etc/xrdp/xrdp.ini)"
35
+ elif command -v ss >/dev/null 2>&1 && ! ss -ltnH "sport = :$P" 2>/dev/null | grep -q .; then
36
+ XRDP_WHY="xrdp runs, but nothing listens on its port $P"
37
+ else
38
+ XRDP_PORT=$P
39
+ fi
40
+ fi
41
+ NO_GNOME=
42
+ command -v grdctl >/dev/null 2>&1 || NO_GNOME="GNOME Remote Desktop (grdctl) is not installed"
43
+ [ -n "$NO_GNOME" ] || command -v gnome-shell >/dev/null 2>&1 || NO_GNOME="gnome-shell is not installed"
44
+ if [ -n "$NO_GNOME" ]; then
45
+ # No GNOME: the host's own desktop is xrdp's, if it runs. Nothing to set up for it: xrdp starts a session
46
+ # when the user signs in.
47
+ [ -z "$XRDP_PORT" ] || { say "RD_XRDP port=$XRDP_PORT user=$(id -un)"; exit 0; }
48
+ [ -z "$XRDP_WHY" ] || fail "$NO_GNOME, and $XRDP_WHY"
49
+ fail "$NO_GNOME. For other Linux desktops, install xrdp and a desktop (Debian, Ubuntu: sudo apt install xrdp xfce4)"
50
+ fi
15
51
  if systemctl --user is-active -q gnome-remote-desktop.service; then
16
52
  fail "a desktop-session GNOME Remote Desktop is already running for this user"
17
53
  fi
@@ -28,10 +64,15 @@ for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
28
64
  fi
29
65
  done
30
66
  mkdir -p "$D" && chmod 700 "$D" || fail "could not create $D"
67
+ NEW_CERT=0
31
68
  if [ ! -s "$D/tls.key" ]; then
32
69
  openssl req -new -newkey rsa:3072 -days 3650 -nodes -x509 -subj "/CN=$(hostname)" \
33
70
  -keyout "$D/tls.key" -out "$D/tls.crt" >/dev/null 2>&1 || fail "could not create a TLS certificate"
71
+ NEW_CERT=1
34
72
  fi
73
+ # The plugin accepts only this certificate on grd's port.
74
+ CERT=$(openssl x509 -in "$D/tls.crt" -noout -fingerprint -sha256 2>/dev/null | sed 's/^[^=]*=//')
75
+ [ -n "$CERT" ] || fail "could not read the TLS certificate $D/tls.crt (openssl x509)"
35
76
  # 'desk' support (setting, off by default): interactive SSH logins start inside a shareable session (trd-pty,
36
77
  # or tmux), so 'desk' can attach this exact console to a terminal on the desktop. Rewritten on every connect;
37
78
  # opt out per login with TABBY_NO_SESSION=1. When the setting is off, the hook and helpers are removed.
@@ -83,7 +124,10 @@ elif [ -n "$TMUX" ]; then
83
124
  SOCKET=$(tmux display-message -p '#{socket_path}' 2>/dev/null)
84
125
  tmux set-option -p allow-passthrough on 2>/dev/null
85
126
  fi
86
- SEQ=$(printf '\033]7777;desk;%s;%s;%s;%s\007' "$(b64 "$SESSION")" "$(b64 "$SOCKET")" "$(b64 "$PWD")" "$(b64 "$KIND")")
127
+ # Which machine this is: a shared home folder puts this script on others too, and the tab may be connected to another
128
+ # machine than the one this runs on (ssh typed in its console).
129
+ MACHINE=$(cat /etc/machine-id 2>/dev/null || hostname)
130
+ SEQ=$(printf '\033]7777;desk;%s;%s;%s;%s;%s;%s\007' "$(b64 "$SESSION")" "$(b64 "$SOCKET")" "$(b64 "$PWD")" "$(b64 "$KIND")" "$(b64 "$MACHINE")" "$(b64 "$(hostname)")")
87
131
  if [ "$KIND" = tmux ]; then
88
132
  printf '\033Ptmux;\033%s\033\\' "$SEQ" # tmux only lets it through as passthrough
89
133
  else
@@ -122,7 +166,9 @@ STATUS=$(grdctl --headless status --show-credentials 2>/dev/null)
122
166
  has () { printf '%s\n' "$STATUS" | grep -qxF "$(printf '\t%s' "$1")"; }
123
167
  g () { grdctl --headless rdp "$@" >/dev/null 2>&1; }
124
168
  RESTART=0
125
- if ! has "TLS key: $D/tls.key" || ! has "TLS certificate: $D/tls.crt"; then
169
+ # grd reads the certificate at startup: a new one at the same path needs a restart too, or grd would keep serving
170
+ # the old one, which the plugin would then refuse.
171
+ if [ $NEW_CERT = 1 ] || ! has "TLS key: $D/tls.key" || ! has "TLS certificate: $D/tls.crt"; then
126
172
  g set-tls-key "$D/tls.key" && g set-tls-cert "$D/tls.crt" || fail "grdctl could not set the TLS certificate"
127
173
  RESTART=1
128
174
  fi
@@ -194,26 +240,59 @@ PORT=$(grdctl --headless status 2>/dev/null | awk '/Port:/ { print $2; exit }')
194
240
  PORT=${"$"}{PORT:-3389}
195
241
  i=0; while [ $i -lt 40 ] && ! ss -ltnH "sport = :$PORT" 2>/dev/null | grep -q .; do sleep 0.25; i=$((i+1)); done
196
242
  ss -ltnH "sport = :$PORT" 2>/dev/null | grep -q . || fail "nothing is listening on port $PORT"
197
- say "RD_OK port=$PORT user=$RD_USER pass=$RD_PASS"
243
+ # xrdp besides GNOME (on a port of its own; on grd's, one of them couldn't listen): offered as another desktop.
244
+ [ -z "$XRDP_PORT" ] || [ "$XRDP_PORT" = "$PORT" ] || say "RD_XRDP port=$XRDP_PORT user=$(id -un)"
245
+ say "RD_OK port=$PORT user=$RD_USER pass=$RD_PASS cert=$CERT"
198
246
  `;
199
247
  let trdPtySource = null;
200
248
  function trdPty() {
201
249
  trdPtySource ??= fs.readFileSync(path.join(__dirname, '..', 'remote', 'trd-pty.py'), 'utf8');
202
250
  return trdPtySource;
203
251
  }
204
- /** Makes sure the remote user has a headless GNOME session served by grd, with fresh credentials. */
252
+ /**
253
+ * Whether the SSH host is Windows, i.e. its shell is cmd.exe or PowerShell. One command each of the three shells answers
254
+ * differently: cmd expands %OS%, PowerShell $env:OS (both to Windows_NT), sh neither.
255
+ */
256
+ async function isWindows(target) {
257
+ try {
258
+ return /Windows_NT/.test(await target.exec('echo %OS% $env:OS', ''));
259
+ }
260
+ catch {
261
+ return false;
262
+ }
263
+ }
264
+ /**
265
+ * Makes sure the remote user has a headless GNOME session served by grd, with fresh credentials. Without GNOME,
266
+ * reports xrdp's port instead, when xrdp runs there; on a Windows host (OpenSSH Server on Windows), Windows' own RDP
267
+ * server, which signs in with the Windows account (the SSH user, to start with).
268
+ */
205
269
  async function prepareRemoteDesktop(target, desk, backend = 'native') {
206
270
  const script = `TRD_DESK=${desk ? 1 : 0}\nTRD_BACKEND=${backend === 'tmux' ? 'tmux' : 'native'}\n` +
207
271
  SETUP_SCRIPT.replace('@@TRD_PTY_PY@@', trdPty().trimEnd());
208
272
  const out = await target.exec('sh -s', script);
273
+ // Without a sh, Windows prints its complaint on stderr, which exec() doesn't return: nothing came back. Only then is
274
+ // it worth asking, so a Linux host costs nothing extra.
275
+ if (/^RD_WINDOWS$/m.test(out) || !/^RD_(OK|XRDP|ERR) /m.test(out) && await isWindows(target)) {
276
+ // Its RDP server makes its own certificate: trusted on first use, like a desktop behind the host.
277
+ return { kind: 'windows', port: 3389, username: target.key.replace(/@[^@]*$/, ''), password: '', certificate: '' };
278
+ }
209
279
  const err = /^RD_ERR (.*)$/m.exec(out);
210
280
  if (err) {
211
281
  throw new Error(err[1]);
212
282
  }
213
- const ok = /^RD_OK port=(\d+) user=(\S+) pass=(\S+)$/m.exec(out);
214
- if (!ok) {
215
- throw new Error('Remote setup gave no result');
283
+ const ok = /^RD_OK port=(\d+) user=(\S+) pass=(\S+) cert=(\S+)$/m.exec(out);
284
+ const xrdp = /^RD_XRDP port=(\d+) user=(\S*)$/m.exec(out);
285
+ if (ok) {
286
+ const certificate = (0, rdcleanpath_1.normalizeFingerprint)(ok[4]);
287
+ if (!certificate) {
288
+ throw new Error(`Remote setup reported no usable certificate fingerprint (${ok[4]})`);
289
+ }
290
+ return { kind: 'gnome', port: Number(ok[1]), username: ok[2], password: ok[3], certificate, xrdpPort: xrdp ? Number(xrdp[1]) : undefined, xrdpUser: xrdp?.[2] };
291
+ }
292
+ if (xrdp) {
293
+ // xrdp makes its own certificate: trusted on first use, like a desktop behind the host.
294
+ return { kind: 'xrdp', port: Number(xrdp[1]), username: xrdp[2], password: '', certificate: '', xrdpPort: Number(xrdp[1]), xrdpUser: xrdp[2] };
216
295
  }
217
- return { port: Number(ok[1]), username: ok[2], password: ok[3] };
296
+ throw new Error('Remote setup gave no result');
218
297
  }
219
298
  //# sourceMappingURL=remoteSetup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remoteSetup.js","sourceRoot":"","sources":["../src/remoteSetup.ts"],"names":[],"mappings":";;AAsNA,oDAaC;AAnOD,yBAAwB;AACxB,6BAA4B;AAS5B,kHAAkH;AAClH,yGAAyG;AACzG,2GAA2G;AAC3G,yGAAyG;AACzG,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4BvB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4DM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+DAwE2C,GAAG;;;;;;;;;;;;;;;;;;;;;;;;OAwB3D,GAAG;;;;CAIT,CAAA;AAKD,IAAI,YAAY,GAAkB,IAAI,CAAA;AACtC,SAAS,MAAM;IACX,YAAY,KAAK,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAA;IAC5F,OAAO,YAAY,CAAA;AACvB,CAAC;AAED,qGAAqG;AAC9F,KAAK,UAAU,oBAAoB,CAAE,MAAoB,EAAE,IAAa,EAAE,UAA0B,QAAQ;IAC/G,MAAM,MAAM,GAAG,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAI;QAC9F,YAAY,CAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;IAC9D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtC,IAAI,GAAG,EAAE,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;IACD,MAAM,EAAE,GAAG,2CAA2C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAChE,IAAI,CAAC,EAAE,EAAE,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACpE,CAAC"}
1
+ {"version":3,"file":"remoteSetup.js","sourceRoot":"","sources":["../src/remoteSetup.ts"],"names":[],"mappings":"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"}