136 lines
3.2 KiB
Go
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}
|
|
}
|