89 lines
2.7 KiB
Go
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
|
|
}
|