瀏覽代碼

Merge branch source:main into ctrl_arrows

unxed 1 周之前
父節點
當前提交
e33af135ef
共有 3 個文件被更改,包括 428 次插入109 次删除
  1. 7 0
      packages/fv/src/drivers.inc
  2. 159 72
      packages/fv/src/menus.inc
  3. 262 37
      packages/rtl-console/src/unix/keyboard.pp

+ 7 - 0
packages/fv/src/drivers.inc

@@ -1358,6 +1358,13 @@ begin
            $e00d : keycode:=kbEnter;
          end;
        end;
+     if (essAlt in key.ShiftState) and (key.UnicodeChar <> #0) then
+     begin
+       // FIX: For Alt+Key combinations, build the KeyCode using the Unicode character
+       // instead of relying on the scancode-based character, which is wrong for non-latin layouts.
+       // The high byte keeps the scan code, the low byte gets the actual character code.
+       keycode := (keycode and $FF00) or (ord(key.UnicodeChar) and $FF);
+     end;
      Event.What:=evKeyDown;
      Event.KeyCode:=keycode;
      Event.CharCode:=chr(keycode and $ff);

+ 159 - 72
packages/fv/src/menus.inc

@@ -79,9 +79,9 @@ USES
    {$ENDIF}
 
 {$ifdef FV_UNICODE}
-   System.Objects, FreeVision.Udrivers, FreeVision.Uviews, FreeVision.Ufvcommon, FreeVision.Fvconsts;               { GFV standard units }
+   System.Objects, FreeVision.Udrivers, FreeVision.Uviews, FreeVision.Ufvcommon, FreeVision.Fvconsts, System.SysUtils; { GFV standard units }
 {$else FV_UNICODE}
-   System.Objects, FreeVision.Drivers, FreeVision.Views, FreeVision.Fvcommon, FreeVision.Fvconsts;                 { GFV standard units }
+   System.Objects, FreeVision.Drivers, FreeVision.Views, FreeVision.Fvcommon, FreeVision.Fvconsts;                     { GFV standard units }
 {$endif FV_UNICODE}
 {$ELSE FPC_DOTTEDUNITS}
 USES
@@ -98,7 +98,7 @@ USES
    {$ENDIF}
 
 {$ifdef FV_UNICODE}
-   objects, udrivers, uviews, UFVCommon, fvconsts;               { GFV standard units }
+   objects, udrivers, uviews, UFVCommon, fvconsts, SysUtils;               { GFV standard units }
 {$else FV_UNICODE}
    objects, drivers, views, fvcommon, fvconsts;                 { GFV standard units }
 {$endif FV_UNICODE}
@@ -214,7 +214,11 @@ TYPE
       FUNCTION Execute: Word; Virtual;
       FUNCTION GetHelpCtx: Word; Virtual;
       FUNCTION GetPalette: PPalette; Virtual;
+      {$ifdef FV_UNICODE}
+      FUNCTION FindItem (Ch: WideChar): PMenuItem;
+      {$else}
       FUNCTION FindItem (Ch: AnsiChar): PMenuItem;
+      {$endif}
       FUNCTION HotKey (KeyCode: Word): PMenuItem;
       FUNCTION NewSubView (Var Bounds: TRect; AMenu: PMenu;
         AParentMenu: PMenuView): PMenuView; Virtual;
@@ -533,8 +537,13 @@ END;
 {---------------------------------------------------------------------------}
 FUNCTION TMenuView.Execute: Word;
 TYPE MenuAction = (DoNothing, DoSelect, DoReturn);
-VAR AutoSelect: Boolean; Action: MenuAction; Ch: AnsiChar; Res: Word; R: TRect;
+VAR AutoSelect: Boolean; Action: MenuAction; Res: Word; R: TRect;
   ItemShown, P: PMenuItem; Target: PMenuView; E: TEvent; MouseActive: Boolean;
+  {$ifdef FV_UNICODE}
+  searchChar: WideChar;
+  {$else}
+  searchChar: AnsiChar;
+  {$endif}
 
    PROCEDURE TrackMouse;
    VAR Mouse: TPoint; R: TRect;
@@ -645,49 +654,70 @@ BEGIN
            AND MouseInMenus Then Action := DoReturn;  { Set return action }
          End;
        evKeyDown:
-         Case CtrlToArrow(E.KeyCode) Of               { Check arrow keys }
-           kbUp, kbDown: If (Size.Y <> 1) Then
-             TrackKey(CtrlToArrow(E.KeyCode) = kbDown){ Track keyboard }
-             Else If (E.KeyCode = kbDown) Then        { Down arrow }
-             AutoSelect := True;                      { Select item }
-           kbLeft, kbRight: If (ParentMenu = Nil) Then
-             TrackKey(CtrlToArrow(E.KeyCode)=kbRight) { Track keyboard }
-             Else Action := DoReturn;                 { Set return action }
-           kbHome, kbEnd: If (Size.Y <> 1) Then Begin
-               Current := Menu^.Items;                { Set to first item }
-               If (E.KeyCode = kbEnd) Then            { If the 'end' key }
-                 TrackKey(False);                     { Move to last item }
-             End;
-           kbEnter: Begin
-               If Size.Y = 1 Then AutoSelect := True; { Select item }
-               Action := DoSelect;                    { Return the item }
-             End;
-           kbEsc: Begin
-               Action := DoReturn;                    { Set return action }
-               If (ParentMenu = Nil) OR
-               (ParentMenu^.Size.Y <> 1) Then         { Check parent }
-                 ClearEvent(E);                       { Kill the event }
-             End;
-           Else Target := @Self;                      { Set target as self }
-           Ch := GetAltChar(E.KeyCode);
-           If (Ch = #0) Then Ch := E.CharCode Else
-             Target := TopMenu;                       { Target is top menu }
-           P := Target^.FindItem(Ch);                 { Check for item }
-           If (P = Nil) Then Begin
-             P := TopMenu^.HotKey(E.KeyCode);         { Check for hot key }
-             If (P <> Nil) AND                        { Item valid }
-             CommandEnabled(P^.Command) Then Begin    { Command enabled }
-               Res := P^.Command;                     { Set return command }
-               Action := DoReturn;                    { Set return action }
-             End
-           End Else If Target = @Self Then Begin
-             If Size.Y = 1 Then AutoSelect := True;   { Set auto select }
-             Action := DoSelect;                      { Select item }
-             Current := P;                            { Set current item }
-           End Else If (ParentMenu <> Target) OR
-           (ParentMenu^.Current <> P) Then            { Item different }
-              Action := DoReturn;                     { Set return action }
-         End;
+         begin
+           Case CtrlToArrow(E.KeyCode) Of
+             kbUp, kbDown: If (Size.Y <> 1) Then
+               TrackKey(CtrlToArrow(E.KeyCode) = kbDown)
+               Else If (E.KeyCode = kbDown) Then AutoSelect := True;
+             kbLeft, kbRight: If (ParentMenu = Nil) Then
+               TrackKey(CtrlToArrow(E.KeyCode)=kbRight)
+               Else Action := DoReturn;
+             kbHome, kbEnd: If (Size.Y <> 1) Then Begin
+                 Current := Menu^.Items;
+                 If (E.KeyCode = kbEnd) Then TrackKey(False);
+               End;
+             kbEnter: Begin
+                 If Size.Y = 1 Then AutoSelect := True;
+                 Action := DoSelect;
+               End;
+             kbEsc: Begin
+                 Action := DoReturn;
+                 If (ParentMenu = Nil) OR (ParentMenu^.Size.Y <> 1) Then ClearEvent(E);
+               End;
+           Else
+             begin
+               P := nil;
+               // Only perform hotkey search (both paths) if Alt is actually pressed.
+               if (E.KeyShift and kbAltShift <> 0) then
+               begin
+                 // Path 1: Character-based search (for correct layouts like Russian)
+                 {$ifdef FV_UNICODE}
+                 searchChar := E.UnicodeChar;
+                 {$else}
+                 searchChar := E.CharCode;
+                 {$endif}
+                 if searchChar <> #0 then P := TopMenu^.FindItem(searchChar);
+
+                 // Path 2: Scancode-based search (fallback for EN hotkeys in RU layout)
+                 if P = nil then
+                 begin
+                   {$ifdef FV_UNICODE}
+                   searchChar := WideChar(GetAltChar(E.ScanCode shl 8));
+                   {$else}
+                   searchChar := GetAltChar(E.ScanCode shl 8);
+                   {$endif}
+                   if searchChar <> #0 then P := TopMenu^.FindItem(searchChar);
+                 end;
+               end;
+
+               If P = nil Then Begin
+                 P := TopMenu^.HotKey(E.KeyCode);
+                 If (P <> Nil) AND CommandEnabled(P^.Command) Then Begin
+                   Res := P^.Command;
+                   Action := DoReturn;
+                 End
+               End Else Begin // P was found by one of the paths above
+                 Target := TopMenu; // Hotkeys should always target the top menu bar
+                 If Target = @Self Then Begin
+                   If Size.Y = 1 Then AutoSelect := True;
+                   Action := DoSelect;
+                   Current := P;
+                 End Else If (ParentMenu <> Target) OR (ParentMenu^.Current <> P) Then
+                    Action := DoReturn;
+               end;
+             end;
+           End;
+         end;
        evCommand: If (E.Command = cmMenu) Then Begin  { Menu command }
            AutoSelect := False;                       { Dont select item }
            If (ParentMenu <> Nil) Then
@@ -766,25 +796,57 @@ END;
 {--TMenuView----------------------------------------------------------------}
 {  FindItem -> Platforms DOS/DPMI/WIN/NT/OS2 - Updated 11May98 LdB          }
 {---------------------------------------------------------------------------}
+{$ifndef FV_UNICODE}
 FUNCTION TMenuView.FindItem (Ch: AnsiChar): PMenuItem;
-VAR I: SmallInt; P: PMenuItem;
+VAR I: SmallInt; P: PMenuItem; itemHotkey: AnsiChar;
 BEGIN
-   Ch := UpCase(Ch);                                  { Upper case of AnsiChar }
-   P := Menu^.Items;                                  { First menu item }
-   While (P <> Nil) Do Begin                          { While item valid }
-     If (P^.Name <> Sw_PString_Empty) AND (NOT P^.Disabled)  { Valid enabled cmd }
+   if Ch = #0 then begin FindItem := nil; Exit; end;
+   Ch := UpCase(Ch);
+   P := Menu^.Items;
+   While (P <> Nil) Do Begin
+     If (P^.Name <> Sw_PString_Empty) AND (NOT P^.Disabled)
      Then Begin
-       I := Pos('~', P^.Name Sw_PString_Deref);  { Scan for highlight }
-       If (I <> 0) AND (Ch = UpCase(P^.Name Sw_PString_Deref[I+1]))   { Hotkey AnsiChar found }
-       Then Begin
-         FindItem := P;                               { Return item }
-         Exit;                                        { Now exit }
-       End;
+       I := Pos('~', P^.Name^);
+       If (I > 0) AND (I < Length(P^.Name^)) then
+       begin
+         itemHotkey := UpCase(P^.Name^[I+1]);
+         If Ch = itemHotkey Then
+         Begin
+           FindItem := P;
+           Exit;
+         End;
+       end;
      End;
-     P := P^.Next;                                    { Next item }
+     P := P^.Next;
    End;
-   FindItem := Nil;                                   { No item found }
+   FindItem := Nil;
 END;
+{$else}
+FUNCTION TMenuView.FindItem (Ch: WideChar): PMenuItem;
+VAR I: SmallInt; P: PMenuItem; itemHotkey: WideChar; menuName: UnicodeString;
+BEGIN
+   if Ch = #0 then begin FindItem := nil; Exit; end;
+   P := Menu^.Items;
+   While (P <> Nil) Do Begin
+     If (P^.Name <> Sw_PString_Empty) AND (NOT P^.Disabled)
+     Then Begin
+       menuName := P^.Name;
+       I := Pos('~', menuName);
+       If (I > 0) AND (I < Length(menuName)) then
+       begin
+         itemHotkey := menuName[I+1];
+         If WideUpperCase(String(Ch)) = WideUpperCase(String(itemHotkey)) Then
+         Begin
+           FindItem := P;
+           Exit;
+         End;
+       end;
+     End;
+     P := P^.Next;
+   End;
+   FindItem := Nil;
+END;
+{$endif}
 
 {--TMenuView----------------------------------------------------------------}
 {  HotKey -> Platforms DOS/DPMI/WIN/NT/OS2 - Updated 11May98 LdB            }
@@ -910,18 +972,43 @@ BEGIN
      Case Event.What Of
        evMouseDown: DoSelect;                         { Select menu item }
        evKeyDown:
-         If (FindItem(GetAltChar(Event.KeyCode)) <> Nil)
-         Then DoSelect Else Begin                     { Select menu item }
-           P := HotKey(Event.KeyCode);                { Check for hotkey }
-           If (P <> Nil) AND
-           (CommandEnabled(P^.Command)) Then Begin
-             Event.What := evCommand;                 { Command event }
-             Event.Command := P^.Command;             { Set command event }
-             Event.InfoPtr := Nil;                    { Clear info ptr }
-             PutEvent(Event);                         { Put event on queue }
-             ClearEvent(Event);                       { Clear the event }
-           End;
-         End;
+         begin
+           P := nil;
+           // Only perform hotkey search (both paths) if Alt is actually pressed.
+           if (Event.KeyShift and kbAltShift <> 0) then
+           begin
+             // Path 1: Character-based search
+             {$ifdef FV_UNICODE}
+             if Event.UnicodeChar <> #0 then P := FindItem(Event.UnicodeChar);
+             {$else}
+             if Event.CharCode <> #0 then P := FindItem(Event.CharCode);
+             {$endif}
+
+             // Path 2: Fallback to scancode-based search
+             if P = nil then
+             begin
+               {$ifdef FV_UNICODE}
+               P := FindItem(WideChar(GetAltChar(Event.ScanCode shl 8)));
+               {$else}
+               P := FindItem(GetAltChar(Event.ScanCode shl 8));
+               {$endif}
+             end;
+           end;
+
+           if P <> nil then
+             DoSelect
+           else
+           begin // Fallback for global shortcuts (F-keys)
+             P := HotKey(Event.KeyCode);
+             If (P <> Nil) AND (CommandEnabled(P^.Command)) Then Begin
+               Event.What := evCommand;
+               Event.Command := P^.Command;
+               Event.InfoPtr := Nil;
+               PutEvent(Event);
+               ClearEvent(Event);
+             End;
+           end;
+         end;
        evCommand:
          If Event.Command = cmMenu Then DoSelect;     { Select menu item }
        evBroadcast:

+ 262 - 37
packages/rtl-console/src/unix/keyboard.pp

@@ -29,6 +29,21 @@ const
   AltPrefix : byte = 0;
   ShiftPrefix : byte = 0;
   CtrlPrefix : byte = 0;
+  // Constants for win32-input-mode
+  const
+    RIGHT_ALT_PRESSED       = $0001;
+    LEFT_ALT_PRESSED        = $0002;
+    RIGHT_CTRL_PRESSED      = $0004;
+    LEFT_CTRL_PRESSED       = $0008;
+    SHIFT_PRESSED           = $0010;
+    NUMLOCK_ON              = $0020;
+    SCROLLLOCK_ON           = $0040;
+    CAPSLOCK_ON             = $0080;
+    ENHANCED_KEY            = $0100;
+    kbBack        = $0E08;
+    kbTab         = $0F09;
+    kbEnter       = $1C0D;
+    kbSpaceBar    = $3920;
 
 type
   Tprocedure = procedure;
@@ -2214,6 +2229,125 @@ var
   arrayind : byte;
   SState: TEnhancedShiftState;
 
+    procedure DecodeAndPushWin32Key(const store: array of AnsiChar; arrayind: byte);
+
+      function VKToScanCode(vk: Word): Byte;
+      begin
+        case vk of
+          // Standard keys
+          $41..$5A : VKToScanCode := cScanValue[vk]; // 'A'..'Z'
+          $30..$39 : VKToScanCode := cScanValue[vk]; // '0'..'9'
+          $08: VKToScanCode := kbBack;
+          $09: VKToScanCode := kbTab;
+          $0D: VKToScanCode := kbEnter;
+          $1B: VKToScanCode := kbEsc;
+          $20: VKToScanCode := kbSpaceBar;
+          // Function keys
+          $70..$79: VKToScanCode := vk - $70 + kbF1; // F1-F10
+          $7A..$7B: VKToScanCode := vk - $7A + kbF11; // F11-F12
+          // Navigation keys
+          $2D: VKToScanCode := kbIns;
+          $2E: VKToScanCode := kbDel;
+          $24: VKToScanCode := kbHome;
+          $23: VKToScanCode := kbEnd;
+          $21: VKToScanCode := kbPgUp;
+          $22: VKToScanCode := kbPgDn;
+          $26: VKToScanCode := kbUp;
+          $28: VKToScanCode := kbDown;
+          $25: VKToScanCode := kbLeft;
+          $27: VKToScanCode := kbRight;
+          // Modifier keys (scancodes for L/R versions)
+          $10: VKToScanCode := $2A; // VK_SHIFT -> Left shift
+          $11: VKToScanCode := $1D; // VK_CONTROL -> Left control
+          $12: VKToScanCode := $38; // VK_MENU -> Left alt
+          // Lock keys
+          $14: VKToScanCode := $3A; // VK_CAPITAL
+          $90: VKToScanCode := $45; // VK_NUMLOCK
+          $91: VKToScanCode := $46; // VK_SCROLL
+          // OEM Keys
+          $BA: VKToScanCode := $27; // VK_OEM_1 (;)
+          $BB: VKToScanCode := $0D; // VK_OEM_PLUS (=)
+          $BC: VKToScanCode := $33; // VK_OEM_COMMA (,)
+          $BD: VKToScanCode := $0C; // VK_OEM_MINUS (-)
+          $BE: VKToScanCode := $34; // VK_OEM_PERIOD (.)
+          $BF: VKToScanCode := $35; // VK_OEM_2 (/)
+          $C0: VKToScanCode := $29; // VK_OEM_3 (`)
+          $DB: VKToScanCode := $1A; // VK_OEM_4 ([)
+          $DC: VKToScanCode := $2B; // VK_OEM_5 (\)
+          $DD: VKToScanCode := $1B; // VK_OEM_6 (])
+          $DE: VKToScanCode := $28; // VK_OEM_7 (')
+        else
+          VKToScanCode := 0;
+        end;
+      end;
+
+    var
+      params: array[0..5] of LongInt; // Vk, Sc, Uc, Kd, Cs, Rc
+      i, p_idx, code: Integer;
+      st: string;
+      ch: AnsiChar;
+      ScanCode: Byte;
+      k: TEnhancedKeyEvent;
+    begin
+      // 1. Parse the parameters: Vk;Sc;Uc;Kd;Cs;Rc
+      for i := 0 to 5 do params[i] := 0; // Clear params
+      params[5] := 1; // Default repeat count is 1
+
+      p_idx := 0;
+      st := '';
+      // Start from after the CSI: ^[[
+      for i := 2 to arrayind - 2 do
+      begin
+        ch := store[i];
+        if ch = ';' then
+        begin
+          if st <> '' then Val(st, params[p_idx], code);
+          st := '';
+          Inc(p_idx);
+          if p_idx > 5 then Break;
+        end
+        else if ch in ['0'..'9'] then
+          st := st + ch;
+      end;
+      // Last parameter
+      if (p_idx <= 5) and (st <> '') then
+        Val(st, params[p_idx], code);
+
+      // 2. Process only key down and repeat events (param[3] must be non-zero)
+      if params[3] = 0 then exit; // Ignore key up events completely for now.
+                                  // The sequence is considered "handled".
+
+      // 3. Create a new key event
+      k := NilEnhancedKeyEvent;
+
+      // 4. Map ControlKeyState (Cs) to ShiftState
+      if (params[4] and SHIFT_PRESSED) <> 0 then Include(k.ShiftState, essShift);
+      if (params[4] and LEFT_CTRL_PRESSED) <> 0 then Include(k.ShiftState, essLeftCtrl);
+      if (params[4] and RIGHT_CTRL_PRESSED) <> 0 then Include(k.ShiftState, essRightCtrl);
+      if (params[4] and (LEFT_CTRL_PRESSED or RIGHT_CTRL_PRESSED)) <> 0 then Include(k.ShiftState, essCtrl);
+      if (params[4] and LEFT_ALT_PRESSED) <> 0 then Include(k.ShiftState, essLeftAlt);
+      if (params[4] and RIGHT_ALT_PRESSED) <> 0 then Include(k.ShiftState, essRightAlt);
+      if (params[4] and (LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED)) <> 0 then Include(k.ShiftState, essAlt);
+
+      // 5. Map Uc, Sc, and Vk
+      k.UnicodeChar := WideChar(params[2]);
+      if params[2] <= 127 then
+        k.AsciiChar := AnsiChar(params[2])
+      else
+        k.AsciiChar := '?';
+
+      ScanCode := params[1]; // wVirtualScanCode
+      if ScanCode = 0 then
+        ScanCode := VKToScanCode(params[0]); // wVirtualKeyCode
+
+      // If we have a char but no special scancode, use the char's scancode
+      if (ScanCode = 0) and (Ord(k.AsciiChar) > 0) and (Ord(k.AsciiChar) < 128) then
+        ScanCode := cScanValue[Ord(k.AsciiChar)];
+
+      k.VirtualScanCode := (ScanCode shl 8) or Ord(k.AsciiChar);
+      PushKey(k);
+    end;
+
     procedure DecodeKittyKey(var k :TEnhancedKeyEvent; var NPT : PTreeElement);
     var i : dword;
         wc: wideChar;
@@ -2231,6 +2365,7 @@ var
         kbDown : byte;
         nKey : longint;
         modifier: longint;
+        shortCutKey: LongInt;
     begin   {
          if arrayind>0 then
          for i:= 0 to arrayind-1 do
@@ -2371,7 +2506,11 @@ var
               nKey:=unicodeCodePoint;
               if (enh[1]>=0) then
                 nKey:=enh[1];
-              BuildKeyEvent(modifier,nKey,nKey);
+
+              shortCutKey := enh[2];
+              if shortCutKey < 0 then
+                shortCutKey := nKey;
+              BuildKeyEvent(modifier, nKey, shortCutKey);
            end;
            arrayind:=0;
         end;
@@ -2455,6 +2594,10 @@ var
   k: TEnhancedKeyEvent;
   UnicodeCodePoint: LongInt;
   i : dword;
+  // Variables for Alt+UTF8 sequence handling
+  ch1: AnsiChar;
+  utf8_bytes_to_read, loop_idx: Integer;
+  full_sequence_ok: boolean;
 begin
 {Check Buffer first}
   if KeySend<>KeyPut then
@@ -2550,9 +2693,32 @@ begin
             dec(arrayind);
             break;
           end;
-          if (arrayind>3) and not (ch in [';',':','0'..'9']) then break; {end of escape sequence}
+          if (arrayind>3) and not (ch in [';',':','0'..'9']) and (ch <> '_') then break; {end of escape sequence}
         end;
 
+        // If we detect what looks like a parameterized CSI sequence (ESC [ digit ...), which is very likely a
+        // win32-input-mode sequence, enter a blocking read loop to fetch the rest of it until the terminator `_` is found.
+        // This avoids depending on timings/timeouts and distinguishes it from simple sequences like arrow keys (ESC [ A).
+        if (arrayind > 2) and (store[0]=#27) and (store[1]='[') and (store[2] in ['0'..'9']) and (store[arrayind-1] <> '_') then
+        begin
+          repeat
+            ch := ttyRecvChar; // This is a blocking read
+            if arrayind < 31 then
+            begin
+              store[arrayind]:=ch;
+              inc(arrayind);
+            end;
+          until (ch = '_') or (arrayind >= 31);
+        end;
+
+        ch := store[arrayind-1];
+
+        if (ch = '_') and (arrayind > 2) and (store[0]=#27) and (store[1]='[') then
+        begin
+          DecodeAndPushWin32Key(store, arrayind);
+          FoundNPT := RootNPT;
+          arrayind := 0;
+        end else
         if (arrayind>3) then
           if (ch = 'u'  )   { for sure kitty keys  or }
               or ( isKittyKeys and  not assigned(FoundNPT) ) {probally kitty keys}
@@ -2565,29 +2731,67 @@ begin
                 end;
             end;
 
-       NPT:=FoundNPT;
-       if assigned(NPT) and NPT^.CanBeTerminal then
+        if not assigned(FoundNPT) then
         begin
-          if assigned(NPT^.SpecialHandler) then
-            begin
-              NPT^.SpecialHandler;
-              k.AsciiChar := #0;
-              k.UnicodeChar := WideChar(#0);
-              k.VirtualScanCode := 0;
-              PushKey(k);
-            end
-          else if (NPT^.CharValue<>0) or (NPT^.ScanValue<>0) then
+          // This handles the case for non-kitty terminals sending ESC + UTF-8 bytes for Alt+key
+          if (arrayind > 1) and (store[0] = #27) and not isKittyKeys then
+          begin
+            ch1 := store[1];
+            utf8_bytes_to_read := DetectUtf8ByteSequenceStart(ch1) - 1;
+            full_sequence_ok := (arrayind - 1) = (utf8_bytes_to_read + 1);
+
+            if full_sequence_ok then
             begin
-              k.AsciiChar := chr(NPT^.CharValue);
-              k.UnicodeChar := WideChar(NPT^.CharValue);
-              k.VirtualScanCode := (NPT^.ScanValue shl 8) or Ord(k.AsciiChar);
-              k.ShiftState:=k.ShiftState+NPT^.ShiftValue;
-              PushKey(k);
+              // Push continuation bytes back to be re-read by ReadUtf8
+              for loop_idx := arrayind - 1 downto 2 do
+                PutBackIntoInBuf(store[loop_idx]);
+
+              UnicodeCodePoint := ReadUtf8(ch1);
+
+              if UnicodeCodePoint > 0 then
+              begin
+                k.ShiftState := [essAlt];
+                k.VirtualScanCode := 0;
+
+                PushUnicodeKey(k, UnicodeCodePoint, ReplacementAsciiChar);
+                ReadKey := PopKey;
+                exit;
+              end
+              else
+              begin
+                // Failed to parse, push everything back as-is
+                PutBackIntoInBuf(ch1);
+                for loop_idx := 2 to arrayind - 1 do
+                  PutBackIntoInBuf(store[loop_idx]);
+              end;
             end;
+          end;
+          RestoreArray;
         end
-      else
-        RestoreArray;
-   end;
+        else
+        NPT:=FoundNPT;
+        if assigned(NPT) and NPT^.CanBeTerminal then
+         begin
+           if assigned(NPT^.SpecialHandler) then
+             begin
+               NPT^.SpecialHandler;
+               k.AsciiChar := #0;
+               k.UnicodeChar := WideChar(#0);
+               k.VirtualScanCode := 0;
+               PushKey(k);
+             end
+           else if (NPT^.CharValue<>0) or (NPT^.ScanValue<>0) then
+             begin
+               k.AsciiChar := chr(NPT^.CharValue);
+               k.UnicodeChar := WideChar(NPT^.CharValue);
+               k.VirtualScanCode := (NPT^.ScanValue shl 8) or Ord(k.AsciiChar);
+               k.ShiftState:=k.ShiftState+NPT^.ShiftValue;
+               PushKey(k);
+             end;
+         end
+       else
+         RestoreArray;
+    end;
 {$ifdef logging}
        writeln(f);
 {$endif logging}
@@ -2647,6 +2851,8 @@ end;
 { Exported functions }
 
 procedure SysInitKeyboard;
+var
+  envInput: string;
 begin
   isKittyKeys:=false;
   CurrentShiftState:=[];
@@ -2684,11 +2890,32 @@ begin
         end;
       {kitty_keys_no:=true;}
       isKittyKeys:=kitty_keys_yes;
-      if kitty_keys_yes or (kitty_keys_yes=kitty_keys_no) then
-         write(#27'[>31u'); { try to set up kitty keys }
-      KittyKeyAvailability;
-      if not isKittyKeys then
-        write(#27'[>4;2m'); { xterm ->  modifyOtherKeys }
+      envInput := fpgetenv('TV_INPUT');
+      if length(envInput) > 0 then
+        envInput[1] := UpCase(envInput[1]);
+
+      if envInput = 'Win32' then
+        begin
+          write(#27'[?9001h');
+        end
+      else if envInput = 'Kitty' then
+        begin
+          write(#27'[>31u');
+          KittyKeyAvailability;
+        end
+      else if envInput = 'Legacy' then
+        begin
+          // Do nothing
+        end
+      else // TV_INPUT not set or incorrect, use default logic
+        begin
+          if kitty_keys_yes or (kitty_keys_yes=kitty_keys_no) then
+             write(#27'[>31u'); { try to set up kitty keys }
+          KittyKeyAvailability;
+          if not isKittyKeys then
+            write(#27'[>4;2m'); { xterm ->  modifyOtherKeys }
+          write(#27'[?9001h'); // Try to enable win32-input-mode
+        end;
 {$ifdef linux}
     end;
 {$endif}
@@ -2703,6 +2930,7 @@ begin
   if is_console then
   unpatchkeyboard;
 {$endif linux}
+  write(#27'[?9001l'); // Disable win32-input-mode
   if not isKittyKeys then
     write(#27'[>4m'); { xterm -> reset to default modifyOtherKeys }
   if kitty_keys_yes then
@@ -2793,7 +3021,15 @@ begin {main}
       exit;
     end;
   SysGetEnhancedKeyEvent:=NilEnhancedKeyEvent;
+  // FAST PATH for pre-constructed events from ReadKey's Alt+UTF8 logic
   MyKey:=ReadKey;
+  // FAST PATH for pre-constructed events from ReadKey's Alt+UTF8 logic OR win32-input-mode
+  if (MyKey.ShiftState <> []) and (MyKey.VirtualScanCode <> 0) then
+  begin
+    SysGetEnhancedKeyEvent := MyKey;
+    LastShiftState := MyKey.ShiftState;
+    exit;
+  end;
   MyChar:=MyKey.AsciiChar;
   MyUniChar:=MyKey.UnicodeChar;
   MyScan:=MyKey.VirtualScanCode shr 8;
@@ -2849,17 +3085,6 @@ begin {main}
         LastShiftState:=SysGetEnhancedKeyEvent.ShiftState; {to fake shift state later}
         exit;
       end
-    else if MyChar=#27 then
-      begin
-        if EscUsed then
-          SState:=SState-[essAlt,essLeftAlt,essRightAlt]
-        else
-          begin
-            Include(SState,essAlt);
-            Again:=true;
-            EscUsed:=true;
-          end;
-      end
     else if (AltPrefix<>0) and (MyChar=chr(AltPrefix)) then
       begin { ^Z - replace Alt for Linux OS }
         if AltPrefixUsed then