Files

89 lines
2.7 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package tentacle
import "git.stormux.org/storm/cthulhu/internal/input"
// Binding proposes a default. It does not acquire a native grab. Scope follows
// the existing global/browse rule: neither may shadow the other in one layout.
type Binding struct {
Layout string `json:"layout"`
Scope string `json:"scope"`
Key string `json:"key"`
}
type BindingStatus struct {
Command string
Suggested Binding
Bound bool
Conflict string // Existing command ID; empty when there is no conflict.
}
func binding_layout(name string) input.KeyboardLayout {
if name == "desktop" {
return input.DesktopKeyboardLayout
}
return input.LaptopKeyboardLayout
}
func validate_binding(binding Binding) error {
if (binding.Layout != "desktop" && binding.Layout != "laptop") ||
(binding.Scope != "global" && binding.Scope != "browse") || len(binding.Key) > 128 {
return ErrProtocol
}
if _, err := input.ParseCommandChord(binding.Key); err != nil {
return ErrProtocol
}
return nil
}
func (session *Session) Bindings() []BindingStatus {
session.mutex.Lock()
defer session.mutex.Unlock()
return append([]BindingStatus(nil), session.bindings...)
}
// Caller holds manager.mutex, but no session lock. Revoked sessions immediately
// stop reserving chords even while their processes are still shutting down.
func (manager *Manager) occupied_bindings() map[input.KeyboardLayout]input.CommandBindings {
occupied := make(map[input.KeyboardLayout]input.CommandBindings)
for layout, bindings := range manager.coreBindings {
occupied[layout] = make(input.CommandBindings)
for chord, command := range bindings {
occupied[layout][chord] = command
}
}
for _, session := range manager.sessions {
session.mutex.Lock()
if session.state == Active {
for _, binding := range session.bindings {
if binding.Bound {
chord, _ := input.ParseCommandChord(binding.Suggested.Key)
occupied[binding_layout(binding.Suggested.Layout)][chord] = input.Command(binding.Command)
}
}
}
session.mutex.Unlock()
}
return occupied
}
func resolve_bindings(identity string, commands []Command, occupied map[input.KeyboardLayout]input.CommandBindings) []BindingStatus {
var result []BindingStatus
for _, command := range commands {
commandID := "tentacle/" + identity + "/" + command.ID
for _, binding := range command.Bindings {
chord, _ := input.ParseCommandChord(binding.Key)
bindings := occupied[binding_layout(binding.Layout)]
previous := bindings[chord]
result = append(result, BindingStatus{Command: commandID, Suggested: binding,
Bound: previous == "", Conflict: string(previous)})
if previous == "" {
bindings[chord] = input.Command(commandID)
}
}
}
return result
}