From f61ff2cd00a7adc66f298e49b05f44d99b3426f1 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 29 Jan 2026 20:32:44 +0000 Subject: [PATCH] fix: clear mobile keyboard modifiers after Enter/Backspace/Delete Modifiers (Ctrl/Shift) were staying active ('sticky') after pressing Enter, Backspace, or Delete keys on the mobile soft keyboard. The beforeinput event handler was sending the key sequences but not calling deactivateModifiers(), unlike the input handler (for regular text) and keydown handler (for arrow keys). This caused modifiers to remain highlighted and active until the user typed a regular letter, making the keyboard feel unresponsive. Now modifiers are properly cleared after all soft keyboard keys. --- src/webterm/static/js/terminal.js | 2 +- src/webterm/static/js/terminal.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webterm/static/js/terminal.js b/src/webterm/static/js/terminal.js index 788d400..d0a2370 100644 --- a/src/webterm/static/js/terminal.js +++ b/src/webterm/static/js/terminal.js @@ -25,7 +25,7 @@ For tests, pass a Ghostty instance directly: resize: none; font-size: 16px; caret-color: transparent; - `,this.element.style.position="relative",this.element.appendChild(V),this.mobileInput=V,V.addEventListener("beforeinput",(j)=>{let $=null;switch(j.inputType){case"insertLineBreak":$="\r";break;case"deleteContentBackward":$="";break;case"deleteContentForward":$="\x1B[3~";break}if($)j.preventDefault(),this.send(["stdin",$])}),V.addEventListener("input",()=>{let j=V.value;if(j){let $=j;if(this.shiftActive&&j.length===1)$=j.toUpperCase();if(this.ctrlActive&&j.length===1){let P=$.toUpperCase().charCodeAt(0);if(P>=65&&P<=90)$=String.fromCharCode(P-64)}this.send(["stdin",$]),V.value="",this.deactivateModifiers()}}),V.addEventListener("keydown",(j)=>{let $=j.ctrlKey||this.ctrlActive,P=j.shiftKey||this.shiftActive;if(j.ctrlKey&&j.key.length===1&&!j.altKey&&!j.metaKey){let R=j.key.toUpperCase().charCodeAt(0);if(R>=65&&R<=90){j.preventDefault(),this.send(["stdin",String.fromCharCode(R-64)]);return}}let X=null,K=!1;switch(j.key){case"Escape":X="\x1B",K=!0;break;case"ArrowUp":case"ArrowDown":case"ArrowRight":case"ArrowLeft":{let R=j.key==="ArrowUp"?"A":j.key==="ArrowDown"?"B":j.key==="ArrowRight"?"C":"D";if($&&P)X=`\x1B[1;6${R}`;else if($)X=`\x1B[1;5${R}`;else if(P)X=`\x1B[1;2${R}`;else X=`\x1B[${R}`;K=!0;break}case"Tab":if(P)X="\x1B[Z";else X="\t";j.preventDefault(),K=!0;break}if(X){if(j.preventDefault(),this.send(["stdin",X]),K)this.deactivateModifiers()}});let Z=()=>{this.mobileInput?.focus()};this.element.addEventListener("touchend",Z,{passive:!0}),this.element.addEventListener("click",Z)}setupMobileKeybar(){let V=document.createElement("div");V.className="mobile-keybar",V.innerHTML=` + `,this.element.style.position="relative",this.element.appendChild(V),this.mobileInput=V,V.addEventListener("beforeinput",(j)=>{let $=null;switch(j.inputType){case"insertLineBreak":$="\r";break;case"deleteContentBackward":$="";break;case"deleteContentForward":$="\x1B[3~";break}if($)j.preventDefault(),this.send(["stdin",$]),this.deactivateModifiers()}),V.addEventListener("input",()=>{let j=V.value;if(j){let $=j;if(this.shiftActive&&j.length===1)$=j.toUpperCase();if(this.ctrlActive&&j.length===1){let P=$.toUpperCase().charCodeAt(0);if(P>=65&&P<=90)$=String.fromCharCode(P-64)}this.send(["stdin",$]),V.value="",this.deactivateModifiers()}}),V.addEventListener("keydown",(j)=>{let $=j.ctrlKey||this.ctrlActive,P=j.shiftKey||this.shiftActive;if(j.ctrlKey&&j.key.length===1&&!j.altKey&&!j.metaKey){let R=j.key.toUpperCase().charCodeAt(0);if(R>=65&&R<=90){j.preventDefault(),this.send(["stdin",String.fromCharCode(R-64)]);return}}let X=null,K=!1;switch(j.key){case"Escape":X="\x1B",K=!0;break;case"ArrowUp":case"ArrowDown":case"ArrowRight":case"ArrowLeft":{let R=j.key==="ArrowUp"?"A":j.key==="ArrowDown"?"B":j.key==="ArrowRight"?"C":"D";if($&&P)X=`\x1B[1;6${R}`;else if($)X=`\x1B[1;5${R}`;else if(P)X=`\x1B[1;2${R}`;else X=`\x1B[${R}`;K=!0;break}case"Tab":if(P)X="\x1B[Z";else X="\t";j.preventDefault(),K=!0;break}if(X){if(j.preventDefault(),this.send(["stdin",X]),K)this.deactivateModifiers()}});let Z=()=>{this.mobileInput?.focus()};this.element.addEventListener("touchend",Z,{passive:!0}),this.element.addEventListener("click",Z)}setupMobileKeybar(){let V=document.createElement("div");V.className="mobile-keybar",V.innerHTML=` diff --git a/src/webterm/static/js/terminal.ts b/src/webterm/static/js/terminal.ts index 3d17fea..5552939 100644 --- a/src/webterm/static/js/terminal.ts +++ b/src/webterm/static/js/terminal.ts @@ -654,6 +654,8 @@ class WebTerminal { if (seq) { e.preventDefault(); this.send(["stdin", seq]); + // Clear modifiers after sending special keys from soft keyboard + this.deactivateModifiers(); } });