pinokiod 3.270.0 → 3.272.0
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.
- package/kernel/ansi_stream_tracker.js +115 -0
- package/kernel/api/app/index.js +422 -0
- package/kernel/api/htmlmodal/index.js +94 -0
- package/kernel/app_launcher/index.js +115 -0
- package/kernel/app_launcher/platform/base.js +276 -0
- package/kernel/app_launcher/platform/linux.js +229 -0
- package/kernel/app_launcher/platform/macos.js +163 -0
- package/kernel/app_launcher/platform/unsupported.js +34 -0
- package/kernel/app_launcher/platform/windows.js +247 -0
- package/kernel/bin/conda-meta.js +93 -0
- package/kernel/bin/conda.js +2 -4
- package/kernel/bin/index.js +2 -4
- package/kernel/index.js +9 -2
- package/kernel/peer.js +186 -19
- package/kernel/shell.js +212 -1
- package/package.json +1 -1
- package/server/index.js +491 -6
- package/server/public/common.js +224 -741
- package/server/public/create-launcher.js +754 -0
- package/server/public/htmlmodal.js +292 -0
- package/server/public/logs.js +715 -0
- package/server/public/resizeSync.js +117 -0
- package/server/public/style.css +651 -6
- package/server/public/tab-idle-notifier.js +34 -59
- package/server/public/tab-link-popover.js +7 -10
- package/server/public/terminal-settings.js +723 -9
- package/server/public/terminal_input_utils.js +72 -0
- package/server/public/terminal_key_caption.js +187 -0
- package/server/public/urldropdown.css +120 -3
- package/server/public/xterm-inline-bridge.js +116 -0
- package/server/socket.js +29 -0
- package/server/views/agents.ejs +1 -2
- package/server/views/app.ejs +55 -28
- package/server/views/bookmarklet.ejs +1 -1
- package/server/views/bootstrap.ejs +1 -0
- package/server/views/connect.ejs +1 -2
- package/server/views/create.ejs +63 -0
- package/server/views/editor.ejs +36 -4
- package/server/views/index.ejs +1 -2
- package/server/views/index2.ejs +1 -2
- package/server/views/init/index.ejs +36 -28
- package/server/views/install.ejs +20 -22
- package/server/views/layout.ejs +2 -8
- package/server/views/logs.ejs +155 -0
- package/server/views/mini.ejs +0 -18
- package/server/views/net.ejs +2 -2
- package/server/views/network.ejs +1 -2
- package/server/views/network2.ejs +1 -2
- package/server/views/old_network.ejs +1 -2
- package/server/views/pro.ejs +26 -23
- package/server/views/prototype/index.ejs +30 -34
- package/server/views/screenshots.ejs +1 -2
- package/server/views/settings.ejs +1 -20
- package/server/views/shell.ejs +59 -66
- package/server/views/terminal.ejs +118 -73
- package/server/views/tools.ejs +1 -2
|
@@ -59,17 +59,639 @@
|
|
|
59
59
|
const THEME_KEY_ALIASES = {
|
|
60
60
|
selection: 'selectionBackground'
|
|
61
61
|
};
|
|
62
|
+
const DIRECT_TYPING_PREF_KEY = 'pinokio.terminal.directTyping';
|
|
62
63
|
|
|
63
64
|
function isFiniteNumber(value) {
|
|
64
65
|
return typeof value === 'number' && Number.isFinite(value);
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
class TerminalMobileInput {
|
|
69
|
+
constructor(settings) {
|
|
70
|
+
this.settings = settings;
|
|
71
|
+
this.runnerButtons = new WeakMap();
|
|
72
|
+
this.termRecords = new Map();
|
|
73
|
+
this.modal = null;
|
|
74
|
+
this.backdrop = null;
|
|
75
|
+
this.textarea = null;
|
|
76
|
+
this.newlineCheckbox = null;
|
|
77
|
+
this.statusElement = null;
|
|
78
|
+
this.statusTimer = null;
|
|
79
|
+
this.modalOpen = false;
|
|
80
|
+
this.lastTrigger = null;
|
|
81
|
+
this.tapTrackers = new WeakMap();
|
|
82
|
+
this.supportsPointer = typeof window !== 'undefined' && 'PointerEvent' in window;
|
|
83
|
+
this.escapeHandler = (event) => {
|
|
84
|
+
if (!event || event.key !== 'Escape' || !this.modalOpen) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
this.closeModal();
|
|
89
|
+
};
|
|
90
|
+
const stored = this.loadDirectTypingPreference();
|
|
91
|
+
if (stored === null) {
|
|
92
|
+
this.directTypingEnabled = !this.shouldPreferModalInput();
|
|
93
|
+
} else {
|
|
94
|
+
this.directTypingEnabled = stored;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
shouldPreferModalInput() {
|
|
99
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
100
|
+
try {
|
|
101
|
+
if (window.matchMedia('(pointer: coarse)').matches) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
} catch (_) {}
|
|
105
|
+
try {
|
|
106
|
+
if (window.matchMedia('(max-width: 768px)').matches) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
} catch (_) {}
|
|
110
|
+
}
|
|
111
|
+
if (typeof navigator !== 'undefined') {
|
|
112
|
+
const ua = navigator.userAgent || '';
|
|
113
|
+
if (/Mobi|Android|iPhone|iPad|Tablet/i.test(ua)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
loadDirectTypingPreference() {
|
|
121
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const stored = window.localStorage.getItem(DIRECT_TYPING_PREF_KEY);
|
|
126
|
+
if (stored === '1') {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (stored === '0') {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
} catch (_) {}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
saveDirectTypingPreference(value) {
|
|
137
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
if (typeof value === 'boolean') {
|
|
142
|
+
window.localStorage.setItem(DIRECT_TYPING_PREF_KEY, value ? '1' : '0');
|
|
143
|
+
} else {
|
|
144
|
+
window.localStorage.removeItem(DIRECT_TYPING_PREF_KEY);
|
|
145
|
+
}
|
|
146
|
+
} catch (_) {}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
registerTerminal(term) {
|
|
150
|
+
if (!term || this.termRecords.has(term)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const record = {
|
|
154
|
+
term,
|
|
155
|
+
textarea: null,
|
|
156
|
+
renderDisposable: null
|
|
157
|
+
};
|
|
158
|
+
this.termRecords.set(term, record);
|
|
159
|
+
if (!term._pinokioMobileInputPatchedOpen && typeof term.open === 'function') {
|
|
160
|
+
const originalOpen = term.open;
|
|
161
|
+
const mobileInput = this;
|
|
162
|
+
term.open = function patchedOpen(...args) {
|
|
163
|
+
const result = originalOpen.apply(this, args);
|
|
164
|
+
try {
|
|
165
|
+
mobileInput.configureTapListener(term);
|
|
166
|
+
} catch (_) {}
|
|
167
|
+
return result;
|
|
168
|
+
};
|
|
169
|
+
term._pinokioMobileInputPatchedOpen = true;
|
|
170
|
+
}
|
|
171
|
+
const capture = () => {
|
|
172
|
+
if (!this.termRecords.has(term)) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
const textarea = this.getTermTextarea(term);
|
|
176
|
+
if (!textarea) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
record.textarea = textarea;
|
|
180
|
+
this.applyInputPolicy(textarea);
|
|
181
|
+
return true;
|
|
182
|
+
};
|
|
183
|
+
if (!capture()) {
|
|
184
|
+
if (typeof term.onRender === 'function') {
|
|
185
|
+
record.renderDisposable = term.onRender(() => {
|
|
186
|
+
if (capture() && record.renderDisposable && typeof record.renderDisposable.dispose === 'function') {
|
|
187
|
+
record.renderDisposable.dispose();
|
|
188
|
+
record.renderDisposable = null;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (!record.renderDisposable) {
|
|
193
|
+
let attempts = 0;
|
|
194
|
+
const poll = () => {
|
|
195
|
+
if (!this.termRecords.has(term) || record.textarea) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
attempts += 1;
|
|
199
|
+
if (capture()) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (attempts < 60) {
|
|
203
|
+
setTimeout(poll, 100);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
setTimeout(poll, 50);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.configureTapListener(term);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
unregisterTerminal(term) {
|
|
213
|
+
const record = this.termRecords.get(term);
|
|
214
|
+
if (!record) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (record.renderDisposable && typeof record.renderDisposable.dispose === 'function') {
|
|
218
|
+
record.renderDisposable.dispose();
|
|
219
|
+
}
|
|
220
|
+
this.termRecords.delete(term);
|
|
221
|
+
this.removeTapListener(term);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
getTermTextarea(term) {
|
|
225
|
+
if (!term) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
if (term.textarea && typeof term.textarea.focus === 'function') {
|
|
229
|
+
return term.textarea;
|
|
230
|
+
}
|
|
231
|
+
if (term._core && term._core._textarea && typeof term._core._textarea.focus === 'function') {
|
|
232
|
+
return term._core._textarea;
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
applyInputPolicy(textarea) {
|
|
238
|
+
if (!textarea) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (this.directTypingEnabled) {
|
|
242
|
+
textarea.removeAttribute('inputmode');
|
|
243
|
+
textarea.removeAttribute('readonly');
|
|
244
|
+
textarea.removeAttribute('aria-readonly');
|
|
245
|
+
} else {
|
|
246
|
+
textarea.setAttribute('inputmode', 'none');
|
|
247
|
+
textarea.setAttribute('readonly', 'readonly');
|
|
248
|
+
textarea.setAttribute('aria-readonly', 'true');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
applyPolicyToAll() {
|
|
253
|
+
this.termRecords.forEach((record) => {
|
|
254
|
+
if (record && record.textarea) {
|
|
255
|
+
this.applyInputPolicy(record.textarea);
|
|
256
|
+
}
|
|
257
|
+
if (record && record.term) {
|
|
258
|
+
this.configureTapListener(record.term);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
setDirectTypingEnabled(enabled) {
|
|
264
|
+
const next = Boolean(enabled);
|
|
265
|
+
if (next === this.directTypingEnabled) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.directTypingEnabled = next;
|
|
269
|
+
this.saveDirectTypingPreference(next);
|
|
270
|
+
this.applyPolicyToAll();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
attachKeyboardButton(runner, host) {
|
|
274
|
+
if (!runner || this.runnerButtons.has(runner)) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const button = document.createElement('button');
|
|
278
|
+
button.type = 'button';
|
|
279
|
+
button.className = 'btn terminal-keyboard-button';
|
|
280
|
+
button.innerHTML = '<i class="fa-solid fa-keyboard"></i> Input';
|
|
281
|
+
button.addEventListener('click', (event) => {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
this.lastTrigger = button;
|
|
284
|
+
this.openModal();
|
|
285
|
+
});
|
|
286
|
+
if (host && host.firstChild) {
|
|
287
|
+
host.insertBefore(button, host.firstChild);
|
|
288
|
+
} else if (host) {
|
|
289
|
+
host.appendChild(button);
|
|
290
|
+
} else {
|
|
291
|
+
runner.appendChild(button);
|
|
292
|
+
}
|
|
293
|
+
this.runnerButtons.set(runner, { button });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
ensureModalElements() {
|
|
297
|
+
if (this.modal || typeof document === 'undefined' || !document.body) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const backdrop = document.createElement('div');
|
|
301
|
+
backdrop.className = 'terminal-keyboard-backdrop';
|
|
302
|
+
backdrop.hidden = true;
|
|
303
|
+
|
|
304
|
+
const modal = document.createElement('div');
|
|
305
|
+
modal.className = 'terminal-keyboard-modal';
|
|
306
|
+
modal.setAttribute('role', 'dialog');
|
|
307
|
+
modal.setAttribute('aria-modal', 'true');
|
|
308
|
+
modal.setAttribute('aria-labelledby', 'terminal-keyboard-title');
|
|
309
|
+
modal.hidden = true;
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
const form = document.createElement('form');
|
|
314
|
+
form.className = 'terminal-keyboard-form';
|
|
315
|
+
form.addEventListener('submit', (event) => {
|
|
316
|
+
event.preventDefault();
|
|
317
|
+
this.submitInput();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const textarea = document.createElement('textarea');
|
|
321
|
+
textarea.className = 'terminal-keyboard-textarea';
|
|
322
|
+
textarea.placeholder = 'Enter command';
|
|
323
|
+
textarea.rows = 4;
|
|
324
|
+
textarea.autocapitalize = 'off';
|
|
325
|
+
textarea.autocomplete = 'off';
|
|
326
|
+
textarea.spellcheck = false;
|
|
327
|
+
textarea.addEventListener('keydown', (event) => {
|
|
328
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
|
329
|
+
event.preventDefault();
|
|
330
|
+
this.submitInput();
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const options = document.createElement('div');
|
|
335
|
+
options.className = 'terminal-keyboard-options';
|
|
336
|
+
|
|
337
|
+
const newlineOption = document.createElement('label');
|
|
338
|
+
newlineOption.className = 'terminal-keyboard-option';
|
|
339
|
+
const newlineCheckbox = document.createElement('input');
|
|
340
|
+
newlineCheckbox.type = 'checkbox';
|
|
341
|
+
newlineCheckbox.className = 'terminal-keyboard-checkbox';
|
|
342
|
+
newlineCheckbox.checked = true;
|
|
343
|
+
const newlineText = document.createElement('span');
|
|
344
|
+
newlineText.textContent = 'Append newline on send';
|
|
345
|
+
newlineOption.appendChild(newlineCheckbox);
|
|
346
|
+
newlineOption.appendChild(newlineText);
|
|
347
|
+
|
|
348
|
+
options.appendChild(newlineOption);
|
|
349
|
+
|
|
350
|
+
const status = document.createElement('div');
|
|
351
|
+
status.className = 'terminal-keyboard-status';
|
|
352
|
+
status.setAttribute('role', 'status');
|
|
353
|
+
status.setAttribute('aria-live', 'polite');
|
|
354
|
+
|
|
355
|
+
const actions = document.createElement('div');
|
|
356
|
+
actions.className = 'terminal-keyboard-actions';
|
|
357
|
+
|
|
358
|
+
const actionsLeft = document.createElement('div');
|
|
359
|
+
actionsLeft.className = 'terminal-keyboard-actions-left';
|
|
360
|
+
|
|
361
|
+
const directTypingButton = document.createElement('button');
|
|
362
|
+
directTypingButton.type = 'button';
|
|
363
|
+
directTypingButton.className = 'btn terminal-keyboard-direct-button';
|
|
364
|
+
directTypingButton.innerHTML = '<i class="fa-solid fa-keyboard"></i> Use Terminal';
|
|
365
|
+
directTypingButton.addEventListener('click', () => this.enableDirectTypingFromModal());
|
|
366
|
+
actionsLeft.appendChild(directTypingButton);
|
|
367
|
+
|
|
368
|
+
const actionsRight = document.createElement('div');
|
|
369
|
+
actionsRight.className = 'terminal-keyboard-actions-right';
|
|
370
|
+
|
|
371
|
+
const cancelButton = document.createElement('button');
|
|
372
|
+
cancelButton.type = 'button';
|
|
373
|
+
cancelButton.className = 'btn2';
|
|
374
|
+
cancelButton.textContent = 'Cancel';
|
|
375
|
+
cancelButton.addEventListener('click', () => this.closeModal());
|
|
376
|
+
|
|
377
|
+
const sendButton = document.createElement('button');
|
|
378
|
+
sendButton.type = 'submit';
|
|
379
|
+
sendButton.className = 'btn terminal-keyboard-send';
|
|
380
|
+
sendButton.innerHTML = '<i class="fa-solid fa-paper-plane"></i> Send';
|
|
381
|
+
|
|
382
|
+
actionsRight.appendChild(cancelButton);
|
|
383
|
+
actionsRight.appendChild(sendButton);
|
|
384
|
+
|
|
385
|
+
actions.appendChild(actionsLeft);
|
|
386
|
+
actions.appendChild(actionsRight);
|
|
387
|
+
|
|
388
|
+
form.appendChild(textarea);
|
|
389
|
+
form.appendChild(options);
|
|
390
|
+
form.appendChild(status);
|
|
391
|
+
form.appendChild(actions);
|
|
392
|
+
|
|
393
|
+
modal.appendChild(form);
|
|
394
|
+
|
|
395
|
+
backdrop.addEventListener('click', () => this.closeModal());
|
|
396
|
+
|
|
397
|
+
document.body.appendChild(backdrop);
|
|
398
|
+
document.body.appendChild(modal);
|
|
399
|
+
|
|
400
|
+
this.modal = modal;
|
|
401
|
+
this.backdrop = backdrop;
|
|
402
|
+
this.textarea = textarea;
|
|
403
|
+
this.newlineCheckbox = newlineCheckbox;
|
|
404
|
+
this.statusElement = status;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
openModal() {
|
|
408
|
+
if (typeof document === 'undefined') {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (!this.modal) {
|
|
412
|
+
this.ensureModalElements();
|
|
413
|
+
}
|
|
414
|
+
if (!this.modal || !this.backdrop) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.setDirectTypingEnabled(false);
|
|
418
|
+
if (this.modalOpen) {
|
|
419
|
+
if (this.textarea) {
|
|
420
|
+
this.textarea.focus();
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
this.modal.hidden = false;
|
|
425
|
+
this.backdrop.hidden = false;
|
|
426
|
+
this.modalOpen = true;
|
|
427
|
+
document.body.classList.add('terminal-keyboard-open');
|
|
428
|
+
document.addEventListener('keydown', this.escapeHandler, true);
|
|
429
|
+
this.setStatus('');
|
|
430
|
+
if (this.textarea) {
|
|
431
|
+
this.textarea.value = '';
|
|
432
|
+
this.textarea.focus();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
closeModal(options) {
|
|
437
|
+
const opts = options || {};
|
|
438
|
+
if (!this.modalOpen) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
this.modalOpen = false;
|
|
442
|
+
if (this.modal) {
|
|
443
|
+
this.modal.hidden = true;
|
|
444
|
+
}
|
|
445
|
+
if (this.backdrop) {
|
|
446
|
+
this.backdrop.hidden = true;
|
|
447
|
+
}
|
|
448
|
+
if (typeof document !== 'undefined') {
|
|
449
|
+
document.body.classList.remove('terminal-keyboard-open');
|
|
450
|
+
document.removeEventListener('keydown', this.escapeHandler, true);
|
|
451
|
+
}
|
|
452
|
+
if (this.textarea) {
|
|
453
|
+
this.textarea.blur();
|
|
454
|
+
this.textarea.value = '';
|
|
455
|
+
}
|
|
456
|
+
this.setStatus('');
|
|
457
|
+
if (opts.focusTrigger !== false && this.lastTrigger && typeof document !== 'undefined' && document.body && document.body.contains(this.lastTrigger)) {
|
|
458
|
+
try {
|
|
459
|
+
this.lastTrigger.focus();
|
|
460
|
+
} catch (_) {}
|
|
461
|
+
}
|
|
462
|
+
this.lastTrigger = null;
|
|
463
|
+
this.configureTapListenerForAll();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
setStatus(message, tone) {
|
|
467
|
+
if (!this.statusElement) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (this.statusTimer) {
|
|
471
|
+
clearTimeout(this.statusTimer);
|
|
472
|
+
this.statusTimer = null;
|
|
473
|
+
}
|
|
474
|
+
const text = typeof message === 'string' ? message : '';
|
|
475
|
+
this.statusElement.textContent = text;
|
|
476
|
+
if (tone) {
|
|
477
|
+
this.statusElement.dataset.tone = tone;
|
|
478
|
+
} else {
|
|
479
|
+
delete this.statusElement.dataset.tone;
|
|
480
|
+
}
|
|
481
|
+
if (text) {
|
|
482
|
+
this.statusElement.classList.add('visible');
|
|
483
|
+
this.statusTimer = setTimeout(() => {
|
|
484
|
+
if (this.statusElement) {
|
|
485
|
+
this.statusElement.classList.remove('visible');
|
|
486
|
+
this.statusElement.textContent = '';
|
|
487
|
+
delete this.statusElement.dataset.tone;
|
|
488
|
+
}
|
|
489
|
+
this.statusTimer = null;
|
|
490
|
+
}, tone === 'error' ? 5000 : 2000);
|
|
491
|
+
} else {
|
|
492
|
+
this.statusElement.classList.remove('visible');
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
focusPrimaryTerminalInput() {
|
|
497
|
+
const term = this.settings && typeof this.settings.getPrimaryTerminal === 'function'
|
|
498
|
+
? this.settings.getPrimaryTerminal()
|
|
499
|
+
: null;
|
|
500
|
+
if (!term) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
const textarea = this.getTermTextarea(term);
|
|
504
|
+
if (textarea) {
|
|
505
|
+
try {
|
|
506
|
+
textarea.focus();
|
|
507
|
+
return true;
|
|
508
|
+
} catch (_) {}
|
|
509
|
+
}
|
|
510
|
+
if (typeof term.focus === 'function') {
|
|
511
|
+
try {
|
|
512
|
+
term.focus();
|
|
513
|
+
return true;
|
|
514
|
+
} catch (_) {}
|
|
515
|
+
}
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
enableDirectTypingFromModal() {
|
|
520
|
+
this.setDirectTypingEnabled(true);
|
|
521
|
+
this.closeModal({ focusTrigger: false });
|
|
522
|
+
this.focusPrimaryTerminalInput();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
submitInput() {
|
|
526
|
+
if (!this.textarea) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const value = this.textarea.value || '';
|
|
530
|
+
const appendNewline = this.newlineCheckbox ? this.newlineCheckbox.checked : true;
|
|
531
|
+
if (!value && !appendNewline) {
|
|
532
|
+
this.setStatus('Enter text or enable newline.', 'error');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const success = this.dispatchToTerminal(value, appendNewline);
|
|
536
|
+
if (!success) {
|
|
537
|
+
this.setStatus('Terminal is not ready yet.', 'error');
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.textarea.value = '';
|
|
541
|
+
this.setStatus('Sent to terminal.', 'success');
|
|
542
|
+
this.closeModal();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
dispatchToTerminal(value, appendNewline) {
|
|
546
|
+
const term = this.settings && typeof this.settings.getPrimaryTerminal === 'function'
|
|
547
|
+
? this.settings.getPrimaryTerminal()
|
|
548
|
+
: null;
|
|
549
|
+
if (!term) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
let payload = typeof value === 'string' ? value : '';
|
|
553
|
+
if (payload) {
|
|
554
|
+
payload = payload.replace(/\r\n/g, '\r').replace(/\n/g, '\r');
|
|
555
|
+
}
|
|
556
|
+
const wantsNewline = Boolean(appendNewline);
|
|
557
|
+
if (wantsNewline) {
|
|
558
|
+
payload = payload.replace(/\r+$/, '');
|
|
559
|
+
}
|
|
560
|
+
const hasText = Boolean(payload);
|
|
561
|
+
if (!hasText && !wantsNewline) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
if (hasText && !this.injectIntoTerminal(term, payload)) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
if (wantsNewline) {
|
|
568
|
+
setTimeout(() => this.injectIntoTerminal(term, '\r'), 100);
|
|
569
|
+
}
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
injectIntoTerminal(term, payload) {
|
|
574
|
+
if (!term) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
let dispatched = false;
|
|
578
|
+
const coreService = term.coreService
|
|
579
|
+
|| (term._core && (term._core.coreService || term._core._coreService))
|
|
580
|
+
|| null;
|
|
581
|
+
if (coreService && typeof coreService.triggerDataEvent === 'function') {
|
|
582
|
+
coreService.triggerDataEvent(payload, true);
|
|
583
|
+
dispatched = true;
|
|
584
|
+
} else if (term._core && term._core._onData && typeof term._core._onData.fire === 'function') {
|
|
585
|
+
term._core._onData.fire(payload);
|
|
586
|
+
dispatched = true;
|
|
587
|
+
} else if (term._onData && typeof term._onData.fire === 'function') {
|
|
588
|
+
term._onData.fire(payload);
|
|
589
|
+
dispatched = true;
|
|
590
|
+
}
|
|
591
|
+
if (!dispatched) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
if (typeof term.focus === 'function') {
|
|
595
|
+
term.focus();
|
|
596
|
+
}
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
configureTapListener(term) {
|
|
601
|
+
if (!term) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const node = term.element || term._core && term._core._terminalDiv || term._core && term._core.element || null;
|
|
605
|
+
if (!node) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (this.directTypingEnabled) {
|
|
609
|
+
this.removeTapListener(term);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (this.tapTrackers.has(term)) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const tracker = {
|
|
616
|
+
lastTime: 0,
|
|
617
|
+
lastX: 0,
|
|
618
|
+
lastY: 0,
|
|
619
|
+
handler: null,
|
|
620
|
+
eventName: this.supportsPointer ? 'pointerdown' : 'touchstart'
|
|
621
|
+
};
|
|
622
|
+
const handlePointerDown = (event) => {
|
|
623
|
+
if (!event) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (this.supportsPointer) {
|
|
627
|
+
const pointerType = event.pointerType;
|
|
628
|
+
if (pointerType && pointerType !== 'touch' && pointerType !== 'pen') {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
} else if (event.touches && event.touches.length !== 1) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const pointSource = this.supportsPointer ? event : (event.touches && event.touches[0]);
|
|
635
|
+
const pointX = pointSource && typeof pointSource.clientX === 'number' ? pointSource.clientX : 0;
|
|
636
|
+
const pointY = pointSource && typeof pointSource.clientY === 'number' ? pointSource.clientY : 0;
|
|
637
|
+
const now = Date.now();
|
|
638
|
+
const delta = now - tracker.lastTime;
|
|
639
|
+
const distance = Math.hypot(pointX - tracker.lastX, pointY - tracker.lastY);
|
|
640
|
+
if (delta < 320 && distance < 40) {
|
|
641
|
+
event.preventDefault();
|
|
642
|
+
event.stopPropagation();
|
|
643
|
+
this.openModalFromGesture();
|
|
644
|
+
}
|
|
645
|
+
tracker.lastTime = now;
|
|
646
|
+
tracker.lastX = pointX;
|
|
647
|
+
tracker.lastY = pointY;
|
|
648
|
+
};
|
|
649
|
+
node.addEventListener(tracker.eventName, handlePointerDown, { passive: false });
|
|
650
|
+
tracker.handler = handlePointerDown;
|
|
651
|
+
tracker.node = node;
|
|
652
|
+
this.tapTrackers.set(term, tracker);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
configureTapListenerForAll() {
|
|
656
|
+
this.termRecords.forEach((record) => {
|
|
657
|
+
if (record && record.term) {
|
|
658
|
+
this.configureTapListener(record.term);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
removeTapListener(term) {
|
|
664
|
+
if (!term) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const tracker = this.tapTrackers.get(term);
|
|
668
|
+
if (!tracker) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (tracker.node && tracker.handler) {
|
|
672
|
+
const eventName = tracker.eventName || (this.supportsPointer ? 'pointerdown' : 'touchstart');
|
|
673
|
+
tracker.node.removeEventListener(eventName, tracker.handler, { passive: false });
|
|
674
|
+
}
|
|
675
|
+
this.tapTrackers.delete(term);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
openModalFromGesture() {
|
|
679
|
+
this.lastTrigger = null;
|
|
680
|
+
this.openModal();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
67
684
|
class TerminalSettings {
|
|
68
685
|
constructor() {
|
|
69
686
|
this.preferences = this.loadPreferences();
|
|
70
687
|
this.terminals = new Set();
|
|
71
688
|
this.menus = new Set();
|
|
72
689
|
this.styleElement = null;
|
|
690
|
+
this.mobileInput = typeof TerminalMobileInput === 'function'
|
|
691
|
+
? new TerminalMobileInput(this)
|
|
692
|
+
: null;
|
|
693
|
+
this.forceResizeButtons = new WeakMap();
|
|
694
|
+
this.forceResizeHandler = null;
|
|
73
695
|
this.currentFontFamily = typeof this.preferences.fontFamily === 'string' ? this.preferences.fontFamily.trim() : '';
|
|
74
696
|
if (typeof document !== 'undefined') {
|
|
75
697
|
const ready = document.readyState;
|
|
@@ -191,10 +813,16 @@
|
|
|
191
813
|
}
|
|
192
814
|
this.terminals.add(term);
|
|
193
815
|
this.applyPreferences(term);
|
|
816
|
+
if (this.mobileInput) {
|
|
817
|
+
this.mobileInput.registerTerminal(term);
|
|
818
|
+
}
|
|
194
819
|
if (!term._pinokioPatchedDispose && typeof term.dispose === 'function') {
|
|
195
820
|
const dispose = term.dispose.bind(term);
|
|
196
821
|
term.dispose = (...args) => {
|
|
197
822
|
this.terminals.delete(term);
|
|
823
|
+
if (this.mobileInput) {
|
|
824
|
+
this.mobileInput.unregisterTerminal(term);
|
|
825
|
+
}
|
|
198
826
|
return dispose(...args);
|
|
199
827
|
};
|
|
200
828
|
term._pinokioPatchedDispose = true;
|
|
@@ -396,10 +1024,6 @@
|
|
|
396
1024
|
}
|
|
397
1025
|
const family = typeof this.preferences.fontFamily === 'string' ? this.preferences.fontFamily.trim() : '';
|
|
398
1026
|
const size = isFiniteNumber(this.preferences.fontSize) ? this.preferences.fontSize : null;
|
|
399
|
-
if (!family && !size) {
|
|
400
|
-
this.removeStyleElement();
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
1027
|
const style = this.ensureStyleElement();
|
|
404
1028
|
if (!style) {
|
|
405
1029
|
return;
|
|
@@ -413,14 +1037,17 @@
|
|
|
413
1037
|
'.xterm .xterm-cursor-layer',
|
|
414
1038
|
'.xterm .xterm-char-measure-element'
|
|
415
1039
|
];
|
|
416
|
-
const
|
|
1040
|
+
const declarations = [
|
|
1041
|
+
'font-variant-ligatures: none !important',
|
|
1042
|
+
'font-feature-settings: "liga" 0, "clig" 0, "calt" 0, "rlig" 0 !important'
|
|
1043
|
+
];
|
|
417
1044
|
if (family) {
|
|
418
|
-
|
|
1045
|
+
declarations.push(`font-family: ${family} !important`);
|
|
419
1046
|
}
|
|
420
1047
|
if (size) {
|
|
421
|
-
|
|
1048
|
+
declarations.push(`font-size: ${size}px !important`);
|
|
422
1049
|
}
|
|
423
|
-
style.textContent =
|
|
1050
|
+
style.textContent = `${selectors.join(', ')} { ${declarations.join('; ')}; }`;
|
|
424
1051
|
}
|
|
425
1052
|
|
|
426
1053
|
sanitizeTheme(raw, allowUnknown) {
|
|
@@ -592,6 +1219,82 @@
|
|
|
592
1219
|
|
|
593
1220
|
warnNonMonospace() {}
|
|
594
1221
|
|
|
1222
|
+
ensureRunnerUtilities(runner) {
|
|
1223
|
+
if (typeof document === 'undefined' || !runner) {
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
let container = runner.querySelector('.terminal-runner-utilities');
|
|
1227
|
+
if (container) {
|
|
1228
|
+
return container;
|
|
1229
|
+
}
|
|
1230
|
+
container = document.createElement('div');
|
|
1231
|
+
container.className = 'terminal-runner-utilities';
|
|
1232
|
+
container.setAttribute('role', 'group');
|
|
1233
|
+
container.setAttribute('aria-label', 'Terminal controls');
|
|
1234
|
+
runner.appendChild(container);
|
|
1235
|
+
return container;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
setForceResizeHandler(handler) {
|
|
1239
|
+
if (typeof handler === 'function') {
|
|
1240
|
+
this.forceResizeHandler = handler;
|
|
1241
|
+
} else {
|
|
1242
|
+
this.forceResizeHandler = null;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
requestForceResize(context) {
|
|
1247
|
+
if (typeof this.forceResizeHandler === 'function') {
|
|
1248
|
+
try {
|
|
1249
|
+
this.forceResizeHandler(context || null);
|
|
1250
|
+
return true;
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
|
1253
|
+
console.warn('Pinokio: force resize handler failed', error);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return false;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
attachForceResizeButton(runner, host) {
|
|
1261
|
+
if (typeof document === 'undefined' || !runner || !host || this.forceResizeButtons.has(runner)) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const button = document.createElement('button');
|
|
1265
|
+
button.type = 'button';
|
|
1266
|
+
button.className = 'btn terminal-resize-button';
|
|
1267
|
+
button.innerHTML = '<i class="fa-solid fa-expand"></i> Resize';
|
|
1268
|
+
button.title = 'Resize to this window';
|
|
1269
|
+
button.addEventListener('click', (event) => {
|
|
1270
|
+
if (event) {
|
|
1271
|
+
event.preventDefault();
|
|
1272
|
+
event.stopPropagation();
|
|
1273
|
+
}
|
|
1274
|
+
const handled = this.requestForceResize({ runner });
|
|
1275
|
+
if (!handled && typeof window !== 'undefined' && typeof window.dispatchEvent === 'function') {
|
|
1276
|
+
try {
|
|
1277
|
+
if (typeof window.CustomEvent === 'function') {
|
|
1278
|
+
window.dispatchEvent(new window.CustomEvent('pinokio-terminal-force-resize', {
|
|
1279
|
+
detail: { runner }
|
|
1280
|
+
}));
|
|
1281
|
+
} else if (typeof document !== 'undefined' && typeof document.createEvent === 'function') {
|
|
1282
|
+
const legacyEvent = document.createEvent('CustomEvent');
|
|
1283
|
+
legacyEvent.initCustomEvent('pinokio-terminal-force-resize', true, true, { runner });
|
|
1284
|
+
window.dispatchEvent(legacyEvent);
|
|
1285
|
+
}
|
|
1286
|
+
} catch (_) {}
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
const configBlock = host.querySelector('.terminal-config');
|
|
1290
|
+
if (configBlock && configBlock.parentNode === host) {
|
|
1291
|
+
host.insertBefore(button, configBlock);
|
|
1292
|
+
} else {
|
|
1293
|
+
host.appendChild(button);
|
|
1294
|
+
}
|
|
1295
|
+
this.forceResizeButtons.set(runner, { button });
|
|
1296
|
+
}
|
|
1297
|
+
|
|
595
1298
|
initRunnerMenus() {
|
|
596
1299
|
if (typeof document === 'undefined') {
|
|
597
1300
|
return;
|
|
@@ -609,10 +1312,18 @@
|
|
|
609
1312
|
return;
|
|
610
1313
|
}
|
|
611
1314
|
runner.dataset.terminalConfigAttached = 'true';
|
|
1315
|
+
const utilities = this.ensureRunnerUtilities(runner);
|
|
612
1316
|
const menu = this.createMenu(runner);
|
|
1317
|
+
if (utilities && menu && menu.wrapper) {
|
|
1318
|
+
utilities.appendChild(menu.wrapper);
|
|
1319
|
+
}
|
|
613
1320
|
if (menu) {
|
|
614
1321
|
this.menus.add(menu);
|
|
615
1322
|
}
|
|
1323
|
+
if (this.mobileInput) {
|
|
1324
|
+
this.mobileInput.attachKeyboardButton(runner, utilities);
|
|
1325
|
+
}
|
|
1326
|
+
this.attachForceResizeButton(runner, utilities);
|
|
616
1327
|
});
|
|
617
1328
|
}
|
|
618
1329
|
|
|
@@ -626,7 +1337,7 @@
|
|
|
626
1337
|
const button = document.createElement('button');
|
|
627
1338
|
button.type = 'button';
|
|
628
1339
|
button.className = 'btn terminal-config-button';
|
|
629
|
-
button.innerHTML = '<i class="fa-solid fa-sliders"></i>
|
|
1340
|
+
button.innerHTML = '<i class="fa-solid fa-sliders"></i>';
|
|
630
1341
|
button.setAttribute('aria-haspopup', 'true');
|
|
631
1342
|
button.setAttribute('aria-expanded', 'false');
|
|
632
1343
|
|
|
@@ -1119,6 +1830,9 @@
|
|
|
1119
1830
|
|
|
1120
1831
|
const settings = new TerminalSettings();
|
|
1121
1832
|
window.PinokioTerminalSettings = settings;
|
|
1833
|
+
if (typeof window !== 'undefined') {
|
|
1834
|
+
window.PinokioTerminalKeyboard = settings && settings.mobileInput ? settings.mobileInput : null;
|
|
1835
|
+
}
|
|
1122
1836
|
|
|
1123
1837
|
if (typeof document !== 'undefined') {
|
|
1124
1838
|
const readyState = document.readyState;
|