// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package input import ( "strings" "sync" "time" ) // Intent records only the kind of input, never the text the user typed. type Intent uint8 const ( NoIntent Intent = iota TypingIntent LineIntent WordIntent CharacterIntent DeleteIntent OtherIntent ) type Activity struct { Selecting bool Intent Intent Sequence uint64 Clipboard ClipboardAction } type ClipboardAction uint8 const ( NoClipboardAction ClipboardAction = iota CopyAction CutAction PasteAction ) type IntentTracker struct { sequence uint64 mutex sync.Mutex held map[string]bool intent Intent clipboard ClipboardAction selecting bool at time.Time } func (tracker *IntentTracker) Observe(event Event) { tracker.mutex.Lock() defer tracker.mutex.Unlock() if event.Pressed { tracker.sequence++ } if tracker.held == nil { tracker.held = make(map[string]bool) } if event.Kind == KeyModifier { tracker.held[event.Name] = event.Pressed if event.Pressed { tracker.intent, tracker.at = OtherIntent, time.Now() tracker.clipboard = NoClipboardAction tracker.selecting = false } return } if !event.Pressed { return } ctrl := tracker.held["Control_L"] || tracker.held["Control_R"] shift := tracker.held["Shift_L"] || tracker.held["Shift_R"] alt := tracker.held["Alt_L"] || tracker.held["Alt_R"] || tracker.held["Meta_L"] || tracker.held["Meta_R"] intent := OtherIntent tracker.clipboard = NoClipboardAction name := strings.TrimPrefix(event.Name, "KP_") if !alt { if ctrl && !shift { switch strings.ToLower(name) { case "c": tracker.clipboard = CopyAction case "x": tracker.clipboard = CutAction case "v": tracker.clipboard = PasteAction } } switch name { case "Up", "Down", "Page_Up", "Page_Down", "Prior", "Next": intent = LineIntent case "Left", "Right": intent = CharacterIntent if ctrl { intent = WordIntent } case "Home", "End": intent = CharacterIntent if ctrl { intent = LineIntent } case "Insert", "Caps_Lock", "Num_Lock", "Scroll_Lock", "Super_L", "Super_R": intent = OtherIntent case "BackSpace", "Delete": intent = DeleteIntent default: if !ctrl && (event.Name != "" && event.Kind == KeyUnknown) { intent = TypingIntent } } } tracker.selecting = shift && (intent == CharacterIntent || intent == WordIntent || intent == LineIntent) tracker.intent, tracker.at = intent, time.Now() } func (tracker *IntentTracker) RecentIntent() Intent { return tracker.RecentInput().Intent } // Reset retires held modifiers and presentation intent across secure input. func (tracker *IntentTracker) Reset() { tracker.mutex.Lock() defer tracker.mutex.Unlock() clear(tracker.held) tracker.intent, tracker.at = OtherIntent, time.Time{} tracker.clipboard, tracker.selecting = NoClipboardAction, false } func (tracker *IntentTracker) RecentInput() Activity { tracker.mutex.Lock() defer tracker.mutex.Unlock() if time.Since(tracker.at) > time.Second { return Activity{} } return Activity{Intent: tracker.intent, Sequence: tracker.sequence, Clipboard: tracker.clipboard, Selecting: tracker.selecting} }