fix: add PointerEvent fallback for swipe detection

touchstart/touchend never fire on this device (confirmed by zero [TOUCH]
log entries). The activity tracker already uses pointerdown/pointerup and
works fine, proving PointerEvents reach the preload. Added pointer event
handlers that mirror the touch handlers for all gestures (2-finger swipe,
3-finger toggle, 1-finger arrow keys). A 500ms debounce on the IPC send
prevents double-firing on devices where both event types fire.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
This commit is contained in:
Claude
2026-06-16 01:59:25 +00:00
parent 8f886854f2
commit 37663be91b
+111 -21
View File
@@ -7165,6 +7165,15 @@ window.addEventListener('DOMContentLoaded',()=>{
} }
},true); },true);
// Shared debounce prevents double-firing when both touch and pointer events fire
let lastSwipeSent=0;
function sendSwipeIPC(direction){
const now=Date.now();
if(now-lastSwipeSent<500)return;
lastSwipeSent=now;
ipcRenderer.send(direction);
}
document.addEventListener('touchstart',e=>{ document.addEventListener('touchstart',e=>{
if(e.touches.length>=1){ if(e.touches.length>=1){
touchStartX=e.touches[0].clientX; touchStartX=e.touches[0].clientX;
@@ -7187,18 +7196,13 @@ window.addEventListener('DOMContentLoaded',()=>{
const absX=Math.abs(deltaX); const absX=Math.abs(deltaX);
const absY=Math.abs(deltaY); const absY=Math.abs(deltaY);
// 3-finger DOWN = toggle hidden tabs (show/hide)
if(fingerCount===3&&absY>SWIPE_THRESHOLD&&absX<SWIPE_TOLERANCE&&deltaY>0){ if(fingerCount===3&&absY>SWIPE_THRESHOLD&&absX<SWIPE_TOLERANCE&&deltaY>0){
console.log('[TOUCH] 3-finger DOWN - toggle hidden tabs'); console.log('[TOUCH] 3-finger DOWN - toggle hidden tabs');
ipcRenderer.send('toggle-hidden'); ipcRenderer.send('toggle-hidden');
} }else if(fingerCount===2&&absX>SWIPE_THRESHOLD&&absY<SWIPE_TOLERANCE){
// 2-finger HORIZONTAL = change tabs
else if(fingerCount===2&&absX>SWIPE_THRESHOLD&&absY<SWIPE_TOLERANCE){
console.log('[TOUCH] 2-finger HORIZONTAL - change tab'); console.log('[TOUCH] 2-finger HORIZONTAL - change tab');
ipcRenderer.send(deltaX>0?'swipe-right':'swipe-left'); sendSwipeIPC(deltaX>0?'swipe-right':'swipe-left');
} }else if(fingerCount===1&&absX>SWIPE_THRESHOLD&&absY<SWIPE_TOLERANCE){
// 1-finger HORIZONTAL = arrow keys
else if(fingerCount===1&&absX>SWIPE_THRESHOLD&&absY<SWIPE_TOLERANCE){
const key=deltaX>0?'ArrowRight':'ArrowLeft'; const key=deltaX>0?'ArrowRight':'ArrowLeft';
const keyCode=deltaX>0?39:37; const keyCode=deltaX>0?39:37;
['keydown','keyup'].forEach(eventType=>{ ['keydown','keyup'].forEach(eventType=>{
@@ -7210,6 +7214,47 @@ window.addEventListener('DOMContentLoaded',()=>{
} }
},{passive:true}); },{passive:true});
// Pointer event fallback — handles devices/drivers where touchstart/touchend don't fire
// (e.g. Electron 42 on some Linux touchscreen drivers that only generate PointerEvents)
let ptrIds=new Set();
let ptrPeak=0;
let ptrStartX=0,ptrStartY=0,ptrStartTime=0;
document.addEventListener('pointerdown',e=>{
if(e.pointerType!=='touch')return;
ptrIds.add(e.pointerId);
if(ptrIds.size===1){ptrStartX=e.clientX;ptrStartY=e.clientY;ptrStartTime=Date.now();ptrPeak=1;}
else{ptrPeak=Math.max(ptrPeak,ptrIds.size);}
},{passive:true});
document.addEventListener('pointerup',e=>{
if(e.pointerType!=='touch')return;
ptrIds.delete(e.pointerId);
if(ptrIds.size!==0)return;
const deltaTime=Date.now()-ptrStartTime;
if(deltaTime>SWIPE_MAX_TIME){ptrPeak=0;return;}
const deltaX=e.clientX-ptrStartX;
const deltaY=e.clientY-ptrStartY;
const absX=Math.abs(deltaX);
const absY=Math.abs(deltaY);
if(ptrPeak===3&&absY>SWIPE_THRESHOLD&&absX<SWIPE_TOLERANCE&&deltaY>0){
console.log('[TOUCH] 3-finger DOWN (ptr) - toggle hidden tabs');
ipcRenderer.send('toggle-hidden');
}else if(ptrPeak===2&&absX>SWIPE_THRESHOLD&&absY<SWIPE_TOLERANCE){
console.log('[TOUCH] 2-finger HORIZONTAL (ptr) - change tab');
sendSwipeIPC(deltaX>0?'swipe-right':'swipe-left');
}else if(ptrPeak===1&&absX>SWIPE_THRESHOLD&&absY<SWIPE_TOLERANCE){
const key=deltaX>0?'ArrowRight':'ArrowLeft';
const keyCode=deltaX>0?39:37;
['keydown','keyup'].forEach(eventType=>{
document.dispatchEvent(new KeyboardEvent(eventType,{
key:key,code:key,keyCode:keyCode,which:keyCode,bubbles:true,cancelable:true
}));
});
}
ptrPeak=0;
},{passive:true});
// Show pause button on user interaction (for rotation sites only) // Show pause button on user interaction (for rotation sites only)
let lastUserInteraction=0; let lastUserInteraction=0;
const USER_INTERACTION_THROTTLE=500; const USER_INTERACTION_THROTTLE=500;
@@ -9182,6 +9227,15 @@ window.addEventListener('DOMContentLoaded',()=>{
console.log(`[KEYBOARD] State: ${visible ? 'visible' : 'hidden'}`); console.log(`[KEYBOARD] State: ${visible ? 'visible' : 'hidden'}`);
}); });
// Shared debounce prevents double-firing when both touch and pointer events fire
let lastSwipeSent=0;
function sendSwipeIPC(direction){
const now=Date.now();
if(now-lastSwipeSent<500)return;
lastSwipeSent=now;
ipcRenderer.send(direction);
}
document.addEventListener('touchstart',e=>{ document.addEventListener('touchstart',e=>{
if(e.touches.length>=1){ if(e.touches.length>=1){
touchStartX=e.touches[0].clientX; touchStartX=e.touches[0].clientX;
@@ -9204,25 +9258,17 @@ window.addEventListener('DOMContentLoaded',()=>{
const absX=Math.abs(deltaX); const absX=Math.abs(deltaX);
const absY=Math.abs(deltaY); const absY=Math.abs(deltaY);
// 3-finger DOWN = toggle hidden tabs (show/hide)
if(fingerCount===3 && absY>SWIPE_THRESHOLD && absX<SWIPE_TOLERANCE && deltaY>0){ if(fingerCount===3 && absY>SWIPE_THRESHOLD && absX<SWIPE_TOLERANCE && deltaY>0){
console.log('[TOUCH] 3-finger DOWN - toggle hidden tabs'); console.log('[TOUCH] 3-finger DOWN - toggle hidden tabs');
ipcRenderer.send('toggle-hidden'); ipcRenderer.send('toggle-hidden');
} }else if(fingerCount===2 && absY>SWIPE_THRESHOLD && absX<SWIPE_TOLERANCE && deltaY>0){
// 2-finger vertical DOWN = keyboard (ALWAYS works, no throttling)
else if(fingerCount===2 && absY>SWIPE_THRESHOLD && absX<SWIPE_TOLERANCE && deltaY>0){
const now = Date.now();
console.log('[TOUCH] 2-finger DOWN - toggle keyboard'); console.log('[TOUCH] 2-finger DOWN - toggle keyboard');
ipcRenderer.send('show-keyboard'); ipcRenderer.send('show-keyboard');
lastKeyboardRequest = now; lastKeyboardRequest=Date.now();
} }else if(fingerCount===2 && absX>SWIPE_THRESHOLD && absY<SWIPE_TOLERANCE){
// 2-finger HORIZONTAL = change tabs
else if(fingerCount===2 && absX>SWIPE_THRESHOLD && absY<SWIPE_TOLERANCE){
console.log('[TOUCH] 2-finger HORIZONTAL - change tab'); console.log('[TOUCH] 2-finger HORIZONTAL - change tab');
ipcRenderer.send(deltaX>0?'swipe-right':'swipe-left'); sendSwipeIPC(deltaX>0?'swipe-right':'swipe-left');
} }else if(fingerCount===1 && absX>SWIPE_THRESHOLD && absY<SWIPE_TOLERANCE){
// 1-finger HORIZONTAL = arrow keys
else if(fingerCount===1 && absX>SWIPE_THRESHOLD && absY<SWIPE_TOLERANCE){
const key=deltaX>0?'ArrowRight':'ArrowLeft'; const key=deltaX>0?'ArrowRight':'ArrowLeft';
const keyCode=deltaX>0?39:37; const keyCode=deltaX>0?39:37;
['keydown','keyup'].forEach(eventType=>{ ['keydown','keyup'].forEach(eventType=>{
@@ -9234,6 +9280,50 @@ window.addEventListener('DOMContentLoaded',()=>{
} }
},{passive:true}); },{passive:true});
// Pointer event fallback — handles devices/drivers where touchstart/touchend don't fire
let ptrIds=new Set();
let ptrPeak=0;
let ptrStartX=0,ptrStartY=0,ptrStartTime=0;
document.addEventListener('pointerdown',e=>{
if(e.pointerType!=='touch')return;
ptrIds.add(e.pointerId);
if(ptrIds.size===1){ptrStartX=e.clientX;ptrStartY=e.clientY;ptrStartTime=Date.now();ptrPeak=1;}
else{ptrPeak=Math.max(ptrPeak,ptrIds.size);}
},{passive:true});
document.addEventListener('pointerup',e=>{
if(e.pointerType!=='touch')return;
ptrIds.delete(e.pointerId);
if(ptrIds.size!==0)return;
const deltaTime=Date.now()-ptrStartTime;
if(deltaTime>SWIPE_MAX_TIME){ptrPeak=0;return;}
const deltaX=e.clientX-ptrStartX;
const deltaY=e.clientY-ptrStartY;
const absX=Math.abs(deltaX);
const absY=Math.abs(deltaY);
if(ptrPeak===3&&absY>SWIPE_THRESHOLD&&absX<SWIPE_TOLERANCE&&deltaY>0){
console.log('[TOUCH] 3-finger DOWN (ptr) - toggle hidden tabs');
ipcRenderer.send('toggle-hidden');
}else if(ptrPeak===2&&absY>SWIPE_THRESHOLD&&absX<SWIPE_TOLERANCE&&deltaY>0){
console.log('[TOUCH] 2-finger DOWN (ptr) - toggle keyboard');
ipcRenderer.send('show-keyboard');
lastKeyboardRequest=Date.now();
}else if(ptrPeak===2&&absX>SWIPE_THRESHOLD&&absY<SWIPE_TOLERANCE){
console.log('[TOUCH] 2-finger HORIZONTAL (ptr) - change tab');
sendSwipeIPC(deltaX>0?'swipe-right':'swipe-left');
}else if(ptrPeak===1&&absX>SWIPE_THRESHOLD&&absY<SWIPE_TOLERANCE){
const key=deltaX>0?'ArrowRight':'ArrowLeft';
const keyCode=deltaX>0?39:37;
['keydown','keyup'].forEach(eventType=>{
document.dispatchEvent(new KeyboardEvent(eventType,{
key:key,code:key,keyCode:keyCode,which:keyCode,bubbles:true,cancelable:true
}));
});
}
ptrPeak=0;
},{passive:true});
// Optional: Auto-show on text field focus (can be disabled) // Optional: Auto-show on text field focus (can be disabled)
let autoShowEnabled = true; // Set to false to disable auto-show let autoShowEnabled = true; // Set to false to disable auto-show