Files

136 lines
3.2 KiB
Go

// 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}
}