fix: more reliable copy/paste with focus polling, clipboard save/restore, toast feedback
This commit is contained in:
+63
-11
@@ -111,7 +111,8 @@ async function executeAction(action) {
|
|||||||
const stealsFocus = ['keystroke', 'copy', 'paste', 'cut'].includes(type);
|
const stealsFocus = ['keystroke', 'copy', 'paste', 'cut'].includes(type);
|
||||||
if (stealsFocus) {
|
if (stealsFocus) {
|
||||||
app.hide();
|
app.hide();
|
||||||
await sleep(80);
|
// Várjuk meg, amíg tényleg az előző app kapja vissza a fókuszt
|
||||||
|
await waitForAppFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -135,9 +136,15 @@ async function executeAction(action) {
|
|||||||
|
|
||||||
/* ─── Clipboard: copy ──────────────────────────────────────────────── */
|
/* ─── Clipboard: copy ──────────────────────────────────────────────── */
|
||||||
case 'copy': {
|
case 'copy': {
|
||||||
await execPromise(`osascript -e 'tell application "System Events" to keystroke "c" using command down'`);
|
// Többször próbálkozunk, mert néha a fókuszváltás + billentyűzés nem elég gyors
|
||||||
await sleep(100);
|
let text = '';
|
||||||
const text = clipboard.readText();
|
for (let attempt = 0; attempt < 3; attempt++) {
|
||||||
|
await execPromise(`osascript -e 'tell application "System Events" to keystroke "c" using command down'`);
|
||||||
|
await sleep(120);
|
||||||
|
text = clipboard.readText();
|
||||||
|
if (text) break;
|
||||||
|
await sleep(50);
|
||||||
|
}
|
||||||
console.log(`Copied: "${text.substring(0, 80)}..."`);
|
console.log(`Copied: "${text.substring(0, 80)}..."`);
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.webContents.send('clipboard-result', { action: 'copy', text });
|
mainWindow.webContents.send('clipboard-result', { action: 'copy', text });
|
||||||
@@ -147,18 +154,35 @@ async function executeAction(action) {
|
|||||||
|
|
||||||
/* ─── Clipboard: paste ─────────────────────────────────────────────── */
|
/* ─── Clipboard: paste ─────────────────────────────────────────────── */
|
||||||
case 'paste': {
|
case 'paste': {
|
||||||
const stored = value || clipboard.readText();
|
// Ha value meg van adva, azt illesztjük be (és utána visszaállítjuk a régit)
|
||||||
clipboard.writeText(stored);
|
if (value) {
|
||||||
await execPromise(`osascript -e 'tell application "System Events" to keystroke "v" using command down'`);
|
const prevClip = clipboard.readText();
|
||||||
|
clipboard.writeText(value);
|
||||||
|
await execPromise(`osascript -e 'tell application "System Events" to keystroke "v" using command down'`);
|
||||||
|
await sleep(150);
|
||||||
|
// Visszaállítjuk az eredeti vágólap tartalmat
|
||||||
|
// (kis késleltetéssel, hogy a cél app befejezze a fogadást)
|
||||||
|
clipboard.writeText(prevClip);
|
||||||
|
} else {
|
||||||
|
// Nincs value – simán illesszük be, ami a vágólapon van
|
||||||
|
await execPromise(`osascript -e 'tell application "System Events" to keystroke "v" using command down'`);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Clipboard: cut ───────────────────────────────────────────────── */
|
/* ─── Clipboard: cut ───────────────────────────────────────────────── */
|
||||||
case 'cut': {
|
case 'cut': {
|
||||||
await execPromise(`osascript -e 'tell application "System Events" to keystroke "x" using command down'`);
|
let text = '';
|
||||||
await sleep(100);
|
for (let attempt = 0; attempt < 3; attempt++) {
|
||||||
|
await execPromise(`osascript -e 'tell application "System Events" to keystroke "x" using command down'`);
|
||||||
|
await sleep(120);
|
||||||
|
text = clipboard.readText();
|
||||||
|
if (text) break;
|
||||||
|
await sleep(50);
|
||||||
|
}
|
||||||
|
console.log(`Cut: "${text.substring(0, 80)}..."`);
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.webContents.send('clipboard-result', { action: 'cut', text: clipboard.readText() });
|
mainWindow.webContents.send('clipboard-result', { action: 'cut', text });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -214,7 +238,8 @@ async function executeAction(action) {
|
|||||||
|
|
||||||
// Visszahozzuk az ablakot
|
// Visszahozzuk az ablakot
|
||||||
if (stealsFocus) {
|
if (stealsFocus) {
|
||||||
await sleep(50);
|
// Várjunk egy kicsit, hogy a cél app megkapja a billentyűzet parancsot
|
||||||
|
await sleep(150);
|
||||||
app.show();
|
app.show();
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
@@ -376,6 +401,33 @@ function execPromise(cmd) {
|
|||||||
|
|
||||||
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Várunk amíg az aktuális fókusz átvált a StreamDeck-ről az előző app-ra.
|
||||||
|
* app.hide() után pollozzuk a frontmost app-ot, max 500ms-ig.
|
||||||
|
*/
|
||||||
|
async function waitForAppFocus() {
|
||||||
|
const maxWait = 800; // max 800ms
|
||||||
|
const interval = 40; // 40ms-ként ellenőrizzük
|
||||||
|
let waited = 0;
|
||||||
|
while (waited < maxWait) {
|
||||||
|
try {
|
||||||
|
const frontApp = execSync(
|
||||||
|
`osascript -e 'tell application "System Events" to get name of first process whose frontmost is true' 2>/dev/null`,
|
||||||
|
{ timeout: 500, encoding: 'utf-8' }
|
||||||
|
).trim();
|
||||||
|
// Ha már nem a StreamDeck az aktív app, kész
|
||||||
|
if (frontApp && frontApp !== 'StreamDeck') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ha hiba van, próbáljuk tovább
|
||||||
|
}
|
||||||
|
await sleep(interval);
|
||||||
|
waited += interval;
|
||||||
|
}
|
||||||
|
// Timeout után is folytatjuk – hátha működik
|
||||||
|
}
|
||||||
|
|
||||||
function parseKeystroke(str) {
|
function parseKeystroke(str) {
|
||||||
if (!str) return null;
|
if (!str) return null;
|
||||||
const parts = str.toLowerCase().split('+');
|
const parts = str.toLowerCase().split('+');
|
||||||
|
|||||||
@@ -446,6 +446,33 @@ document.addEventListener('keydown', (e) => {
|
|||||||
if (e.key === 'ArrowLeft' && currentPage > 0) renderPage(currentPage - 1);
|
if (e.key === 'ArrowLeft' && currentPage > 0) renderPage(currentPage - 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ─── Clipboard result toast ────────────────────────────────────────────── */
|
||||||
|
const toastEl = document.getElementById('toast');
|
||||||
|
let toastTimer = null;
|
||||||
|
|
||||||
|
function showToast(msg, duration) {
|
||||||
|
duration = duration || 2000;
|
||||||
|
if (!toastEl) return;
|
||||||
|
toastEl.textContent = msg;
|
||||||
|
toastEl.style.display = 'block';
|
||||||
|
if (toastTimer) clearTimeout(toastTimer);
|
||||||
|
toastTimer = setTimeout(() => {
|
||||||
|
toastEl.style.display = 'none';
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ELECTRON) {
|
||||||
|
window.electronAPI.onClipboardResult((data) => {
|
||||||
|
if (data.action === 'copy' && data.text) {
|
||||||
|
const short = data.text.length > 60 ? data.text.substring(0, 57) + '…' : data.text;
|
||||||
|
showToast(`Copied: "${short}"`, 2500);
|
||||||
|
} else if (data.action === 'cut' && data.text) {
|
||||||
|
const short = data.text.length > 60 ? data.text.substring(0, 57) + '…' : data.text;
|
||||||
|
showToast(`Cut: "${short}"`, 2500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Document-level event logger (for troubleshooting touch) ──────────── */
|
/* ─── Document-level event logger (for troubleshooting touch) ──────────── */
|
||||||
function docLog(e) {
|
function docLog(e) {
|
||||||
window.debugLog(`📄 ${e.type}`, `target:${e.target?.tagName} pt:${e.pointerType || '-'} btn:${e.which || e.button || 0}`);
|
window.debugLog(`📄 ${e.type}`, `target:${e.target?.tagName} pt:${e.pointerType || '-'} btn:${e.which || e.button || 0}`);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<div id="page-indicator"></div>
|
<div id="page-indicator"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="toast" style="display:none; position:fixed; bottom:60px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.8); color:#fff; padding:10px 20px; border-radius:8px; font:14px sans-serif; max-width:80%; text-align:center; z-index:999; pointer-events:none;"></div>
|
||||||
<div id="debug-overlay" style="display:none; position:fixed; bottom:0; left:0; right:0; background:rgba(0,0,0,0.85); color:#0f0; font:11px monospace; padding:6px; max-height:80px; overflow-y:auto; z-index:999; pointer-events:none;"></div>
|
<div id="debug-overlay" style="display:none; position:fixed; bottom:0; left:0; right:0; background:rgba(0,0,0,0.85); color:#0f0; font:11px monospace; padding:6px; max-height:80px; overflow-y:auto; z-index:999; pointer-events:none;"></div>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user