From 5a68e98db062f2de49f980b8b30a2f9d3aecfae1 Mon Sep 17 00:00:00 2001
From: frazou <4470714+Frazew@users.noreply.github.com>
Date: Sat, 15 Mar 2025 23:14:35 +0100
Subject: [PATCH] Fix selection clearing in kitty keyboard mode

When Kitty's keyboard protocol is used and Report all keys as escape
codes flag (8) is enabled, modifier key escape codes trigger the usual
"write something to the terminal" code path, which clears the selection
/ scrolls down etc.

This behavior is mostly unexpected, and makes some actions more painful
to perform (for instance copying text becomes harder: hitting CTRL to
begin the CTRL+SHIFT+C sequence clears the selection).

This patch clears the selection only if the key event is not a modifier
key, which aligns with Alacritty's usual behavior.

Fixes #8509.
---
 CHANGELOG.md                    |  1 +
 alacritty/src/input/keyboard.rs | 16 +++++++++++++++-
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf13eb7a..4c2cc6cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Notable changes to the `alacritty_terminal` crate are documented in its
 ### Fixed
 
 - Crash when OpenGL context resets
+- Modifier keys clearing selection with kitty keyboard protocol enabled
 
 ## 0.15.1
 
diff --git a/alacritty/src/input/keyboard.rs b/alacritty/src/input/keyboard.rs
index 417f599b..ccdeac3f 100644
--- a/alacritty/src/input/keyboard.rs
+++ b/alacritty/src/input/keyboard.rs
@@ -77,6 +77,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
         let mods = if self.alt_send_esc(&key, text) { mods } else { mods & !ModifiersState::ALT };
 
         let build_key_sequence = Self::should_build_sequence(&key, text, mode, mods);
+        let is_modifier_key = Self::is_modifier_key(&key);
 
         let bytes = if build_key_sequence {
             build_sequence(key, mods, mode)
@@ -92,7 +93,10 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
 
         // Write only if we have something to write.
         if !bytes.is_empty() {
-            self.ctx.on_terminal_input_start();
+            // Don't clear selection/scroll down when writing escaped modifier keys.
+            if !is_modifier_key {
+                self.ctx.on_terminal_input_start();
+            }
             self.ctx.write_to_pty(bytes);
         }
     }
@@ -125,6 +129,16 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
         }
     }
 
+    fn is_modifier_key(key: &KeyEvent) -> bool {
+        matches!(
+            key.logical_key.as_ref(),
+            Key::Named(NamedKey::Shift)
+                | Key::Named(NamedKey::Control)
+                | Key::Named(NamedKey::Alt)
+                | Key::Named(NamedKey::Super)
+        )
+    }
+
     /// Check whether we should try to build escape sequence for the [`KeyEvent`].
     fn should_build_sequence(
         key: &KeyEvent,