Skip to content

Commit 3697682

Browse files
committed
Make clipboard menu items work (especially on mobile)
Fixes #173
1 parent 115dc1b commit 3697682

File tree

2 files changed

+65
-24
lines changed

2 files changed

+65
-24
lines changed

squeak.js

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,8 @@ function recordMouseEvent(what, evt, canvas, display, options) {
226226
display.signalInputEvent();
227227
}
228228
display.idle = 0;
229-
if (what == 'mouseup') {
230-
if (display.runFor) display.runFor(100); // maybe we catch the fullscreen flag change
231-
} else {
232-
if (display.runNow) display.runNow(); // don't wait for timeout to run
233-
}
229+
if (what === 'mouseup') display.runFor(100, what); // process copy/paste or fullscreen flag change
230+
else display.runNow(what); // don't wait for timeout to run
234231
}
235232

236233
function recordWheelEvent(evt, display) {
@@ -253,6 +250,7 @@ function recordWheelEvent(evt, display) {
253250
if (display.signalInputEvent)
254251
display.signalInputEvent();
255252
display.idle = 0;
253+
if (display.runNow) display.runNow('wheel'); // don't wait for timeout to run
256254
}
257255

258256
// Squeak traditional keycodes are MacRoman
@@ -304,7 +302,7 @@ function recordKeyboardEvent(unicode, timestamp, display) {
304302
display.keys.push(modifiersAndKey);
305303
}
306304
display.idle = 0;
307-
if (display.runNow) display.runNow(); // don't wait for timeout to run
305+
if (display.runNow) display.runNow('keyboard'); // don't wait for timeout to run
308306
}
309307

310308
function recordDragDropEvent(type, evt, canvas, display) {
@@ -321,6 +319,8 @@ function recordDragDropEvent(type, evt, canvas, display) {
321319
]);
322320
if (display.signalInputEvent)
323321
display.signalInputEvent();
322+
display.idle = 0;
323+
if (display.runNow) display.runNow('drag-drop'); // don't wait for timeout to run
324324
}
325325

326326
function fakeCmdOrCtrlKey(key, timestamp, display) {
@@ -360,6 +360,7 @@ function createSqueakDisplay(canvas, options) {
360360
eventQueue: null, // only used if image uses event primitives
361361
clipboardString: '',
362362
clipboardStringChanged: false,
363+
handlingEvent: '', // set to 'mouse' or 'keyboard' while handling an event
363364
cursorCanvas: options.cursor !== false && document.getElementById("sqCursor") || document.createElement("canvas"),
364365
cursorOffsetX: 0,
365366
cursorOffsetY: 0,
@@ -446,7 +447,7 @@ function createSqueakDisplay(canvas, options) {
446447
ctx.fillStyle = style.color || "#F90";
447448
ctx.fillRect(x, y, w * value, h);
448449
};
449-
display.executeClipboardPaste = function(text, timestamp) {
450+
display.executeClipboardPasteKey = function(text, timestamp) {
450451
if (!display.vm) return true;
451452
try {
452453
display.clipboardString = text;
@@ -456,7 +457,7 @@ function createSqueakDisplay(canvas, options) {
456457
console.error("paste error " + err);
457458
}
458459
};
459-
display.executeClipboardCopy = function(key, timestamp) {
460+
display.executeClipboardCopyKey = function(key, timestamp) {
460461
if (!display.vm) return true;
461462
// simulate copy event for Squeak so it places its text in clipboard
462463
display.clipboardStringChanged = false;
@@ -863,18 +864,18 @@ function createSqueakDisplay(canvas, options) {
863864
switch (evt.key) {
864865
case "c":
865866
case "x":
866-
if (!navigator.clipboard?.writeText) return; // fire document.oncopy/oncut
867-
var text = display.executeClipboardCopy(evt.key, evt.timeStamp);
867+
if (!navigator.clipboard) return; // fire document.oncopy/oncut
868+
var text = display.executeClipboardCopyKey(evt.key, evt.timeStamp);
868869
if (typeof text === 'string') {
869870
navigator.clipboard.writeText(text)
870871
.catch(function(err) { console.error("display: copy error " + err.message); });
871872
}
872873
return evt.preventDefault();
873874
case "v":
874-
if (!navigator.clipboard?.readText) return; // fire document.onpaste
875+
if (!navigator.clipboard) return; // fire document.onpaste
875876
navigator.clipboard.readText()
876877
.then(function(text) {
877-
display.executeClipboardPaste(text, evt.timeStamp);
878+
display.executeClipboardPasteKey(text, evt.timeStamp);
878879
})
879880
.catch(function(err) { console.error("display: paste error " + err.message); });
880881
return evt.preventDefault();
@@ -892,10 +893,25 @@ function createSqueakDisplay(canvas, options) {
892893
if (!display.vm) return true;
893894
recordModifiers(evt, display);
894895
};
895-
// copy/paste old-style
896-
if (!navigator.clipboard?.writeText) {
896+
// more copy/paste
897+
if (navigator.clipboard) {
898+
// new-style copy/paste (all modern browsers)
899+
display.readFromSystemClipboard = () => navigator.clipboard.readText()
900+
.then(text => display.clipboardString = text)
901+
.catch(err => {
902+
if (!display.handlingEvent) console.warn("reading from clipboard outside event handler");
903+
console.error("readFromSystemClipboard" + err.message);
904+
});
905+
display.writeToSystemClipboard = () => navigator.clipboard.writeText(display.clipboardString)
906+
.then(() => display.clipboardStringChanged = false)
907+
.catch(err => {
908+
if (!display.handlingEvent) console.warn("writing to clipboard outside event handler");
909+
console.error("writeToSystemClipboard" + err.message);
910+
});
911+
} else {
912+
// old-style copy/paste
897913
document.oncopy = function(evt, key) {
898-
var text = display.executeClipboardCopy(key, evt.timeStamp);
914+
var text = display.executeClipboardCopyKey(key, evt.timeStamp);
899915
if (typeof text === 'string') {
900916
evt.clipboardData.setData("Text", text);
901917
}
@@ -905,11 +921,9 @@ function createSqueakDisplay(canvas, options) {
905921
if (!display.vm) return true;
906922
document.oncopy(evt, 'x');
907923
};
908-
}
909-
if (!navigator.clipboard?.readText) {
910924
document.onpaste = function(evt) {
911925
var text = evt.clipboardData.getData('Text');
912-
display.executeClipboardPaste(text, evt.timeStamp);
926+
display.executeClipboardPasteKey(text, evt.timeStamp);
913927
evt.preventDefault();
914928
};
915929
}
@@ -1113,15 +1127,17 @@ SqueakJS.runImage = function(buffer, name, display, options) {
11131127
alert(error);
11141128
}
11151129
}
1116-
display.runNow = function() {
1130+
display.runNow = function(event) {
11171131
window.clearTimeout(loop);
1132+
display.handlingEvent = event;
11181133
run();
1134+
display.handlingEvent = '';
11191135
};
1120-
display.runFor = function(milliseconds) {
1136+
display.runFor = function(milliseconds, event) {
11211137
var stoptime = Date.now() + milliseconds;
11221138
do {
11231139
if (display.quitFlag) return;
1124-
display.runNow();
1140+
display.runNow(event);
11251141
} while (Date.now() < stoptime);
11261142
};
11271143
if (options.onStart) options.onStart(vm, display, options);

vm.input.browser.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,39 @@
2424
Object.extend(Squeak.Primitives.prototype,
2525
'input', {
2626
primitiveClipboardText: function(argCount) {
27+
// There are two ways this primitive is invoked:
28+
// 1: via the DOM keyboard event thandler in squeak.js that intercepts cmd-c/cmd-v,
29+
// reads/writes the system clipboard from/to display.clipboardString
30+
// and then the interpreter calls the primitive
31+
// 2: via the image code e.g. a menu copy/paste item, which calls the primitive
32+
// and we try to read/write the system clipboard directly.
33+
// To support this, squeak.js keeps running the interpreter for 100 ms within
34+
// the DOM event 'mouseup' handler so the code below runs in the click-handler context,
35+
// (otherwise the browser would block access to the clipboard)
2736
if (argCount === 0) { // read from clipboard
28-
if (typeof(this.display.clipboardString) !== 'string') return false;
29-
this.vm.popNandPush(1, this.makeStString(this.display.clipboardString));
37+
// Try to read from system clipboard, which is async if available.
38+
// It will likely fail outside of an event handler.
39+
var clipBoardPromise = null;
40+
if (this.display.readFromSystemClipboard) clipBoardPromise = this.display.readFromSystemClipboard();
41+
if (clipBoardPromise) {
42+
var unfreeze = this.vm.freeze();
43+
clipBoardPromise
44+
.then(() => this.vm.popNandPush(1, this.makeStString(this.display.clipboardString)))
45+
.catch(() => this.vm.popNandPush(1, this.vm.nilObj))
46+
.finally(() => unfreeze());
47+
} else {
48+
if (typeof(this.display.clipboardString) !== 'string') return false;
49+
this.vm.popNandPush(1, this.makeStString(this.display.clipboardString));
50+
}
3051
} else if (argCount === 1) { // write to clipboard
3152
var stringObj = this.vm.top();
3253
if (stringObj.bytes) {
3354
this.display.clipboardString = stringObj.bytesAsString();
34-
this.display.clipboardStringChanged = true;
55+
this.display.clipboardStringChanged = true; // means it should be written to system clipboard
56+
if (this.display.writeToSystemClipboard) {
57+
// no need to wait for the promise
58+
this.display.writeToSystemClipboard();
59+
}
3560
}
3661
this.vm.pop();
3762
}

0 commit comments

Comments
 (0)