mirror of
https://github.com/jixishi/SerialTerminalForWindowsTerminal.git
synced 2026-06-15 16:42:46 +00:00
e0de872740
Extract ConvertChunk/FormatHexFrame into pkg/charset (zero external deps). Extract UIEvent/UIEventKind/UIPanelKind types into internal/event. Update all references across main package to use qualified imports. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
263 lines
5.0 KiB
Go
263 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
type LuaPlugin struct {
|
|
Name string
|
|
Path string
|
|
Enabled bool
|
|
L *lua.LState
|
|
callMu sync.Mutex
|
|
}
|
|
|
|
type PluginSnapshot struct {
|
|
Name string
|
|
Path string
|
|
Enabled bool
|
|
}
|
|
|
|
type PluginManager struct {
|
|
mu sync.RWMutex
|
|
plugins map[string]*LuaPlugin
|
|
}
|
|
|
|
func NewPluginManager() *PluginManager {
|
|
return &PluginManager{plugins: make(map[string]*LuaPlugin)}
|
|
}
|
|
|
|
func (m *PluginManager) Load(path string) (string, error) {
|
|
abs, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
name := strings.TrimSuffix(filepath.Base(abs), filepath.Ext(abs))
|
|
if name == "" {
|
|
return "", fmt.Errorf("invalid plugin name")
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.plugins[name]; ok {
|
|
return "", fmt.Errorf("plugin %s already loaded", name)
|
|
}
|
|
|
|
state := lua.NewState()
|
|
if err = state.DoFile(abs); err != nil {
|
|
state.Close()
|
|
return "", err
|
|
}
|
|
|
|
m.plugins[name] = &LuaPlugin{
|
|
Name: name,
|
|
Path: abs,
|
|
Enabled: true,
|
|
L: state,
|
|
}
|
|
|
|
return name, nil
|
|
}
|
|
|
|
func (m *PluginManager) Unload(name string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.plugins[name]
|
|
if !ok {
|
|
return fmt.Errorf("plugin %s not found", name)
|
|
}
|
|
|
|
p.L.Close()
|
|
delete(m.plugins, name)
|
|
return nil
|
|
}
|
|
|
|
func (m *PluginManager) Enable(name string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.plugins[name]
|
|
if !ok {
|
|
return fmt.Errorf("plugin %s not found", name)
|
|
}
|
|
p.Enabled = true
|
|
return nil
|
|
}
|
|
|
|
func (m *PluginManager) Disable(name string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.plugins[name]
|
|
if !ok {
|
|
return fmt.Errorf("plugin %s not found", name)
|
|
}
|
|
p.Enabled = false
|
|
return nil
|
|
}
|
|
|
|
func (m *PluginManager) Reload(name string) error {
|
|
m.mu.Lock()
|
|
p, ok := m.plugins[name]
|
|
m.mu.Unlock()
|
|
if !ok {
|
|
return fmt.Errorf("plugin %s not found", name)
|
|
}
|
|
|
|
path := p.Path
|
|
if err := m.Unload(name); err != nil {
|
|
return err
|
|
}
|
|
_, err := m.Load(path)
|
|
return err
|
|
}
|
|
|
|
func (m *PluginManager) List() []PluginSnapshot {
|
|
m.mu.RLock()
|
|
res := make([]PluginSnapshot, 0, len(m.plugins))
|
|
for _, p := range m.plugins {
|
|
res = append(res, PluginSnapshot{Name: p.Name, Path: p.Path, Enabled: p.Enabled})
|
|
}
|
|
m.mu.RUnlock()
|
|
|
|
sort.Slice(res, func(i, j int) bool {
|
|
return res[i].Name < res[j].Name
|
|
})
|
|
return res
|
|
}
|
|
|
|
func (m *PluginManager) ProcessInput(data []byte) ([]byte, error) {
|
|
return m.processDataHook("OnInput", data)
|
|
}
|
|
|
|
func (m *PluginManager) ProcessOutput(data []byte) ([]byte, error) {
|
|
return m.processDataHook("OnOutput", data)
|
|
}
|
|
|
|
func (m *PluginManager) processDataHook(name string, data []byte) ([]byte, error) {
|
|
m.mu.RLock()
|
|
plugins := make([]*LuaPlugin, 0, len(m.plugins))
|
|
for _, p := range m.plugins {
|
|
plugins = append(plugins, p)
|
|
}
|
|
m.mu.RUnlock()
|
|
|
|
current := data
|
|
for _, p := range plugins {
|
|
if !p.Enabled {
|
|
continue
|
|
}
|
|
p.callMu.Lock()
|
|
ret, called, err := callStringHook(p.L, name, string(current))
|
|
p.callMu.Unlock()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("plugin %s %s: %w", p.Name, name, err)
|
|
}
|
|
if !called {
|
|
continue
|
|
}
|
|
if ret == nil {
|
|
return nil, nil
|
|
}
|
|
current = []byte(*ret)
|
|
}
|
|
|
|
return current, nil
|
|
}
|
|
|
|
func (m *PluginManager) ProcessCommand(line string) (string, bool, error) {
|
|
m.mu.RLock()
|
|
plugins := make([]*LuaPlugin, 0, len(m.plugins))
|
|
for _, p := range m.plugins {
|
|
plugins = append(plugins, p)
|
|
}
|
|
m.mu.RUnlock()
|
|
|
|
current := line
|
|
allow := true
|
|
for _, p := range plugins {
|
|
if !p.Enabled {
|
|
continue
|
|
}
|
|
p.callMu.Lock()
|
|
next, nextAllow, called, err := callCommandHook(p.L, "OnCommand", current)
|
|
p.callMu.Unlock()
|
|
if err != nil {
|
|
return "", false, fmt.Errorf("plugin %s OnCommand: %w", p.Name, err)
|
|
}
|
|
if !called {
|
|
continue
|
|
}
|
|
allow = allow && nextAllow
|
|
if !allow {
|
|
return "", false, nil
|
|
}
|
|
if next != "" {
|
|
current = next
|
|
}
|
|
}
|
|
|
|
return current, true, nil
|
|
}
|
|
|
|
func (m *PluginManager) Close() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
for _, p := range m.plugins {
|
|
p.L.Close()
|
|
}
|
|
m.plugins = map[string]*LuaPlugin{}
|
|
}
|
|
|
|
func callStringHook(L *lua.LState, name string, payload string) (*string, bool, error) {
|
|
fn := L.GetGlobal(name)
|
|
if fn.Type() == lua.LTNil {
|
|
return nil, false, nil
|
|
}
|
|
|
|
if err := L.CallByParam(lua.P{Fn: fn, NRet: 1, Protect: true}, lua.LString(payload)); err != nil {
|
|
return nil, true, err
|
|
}
|
|
|
|
ret := L.Get(-1)
|
|
L.Pop(1)
|
|
if ret.Type() == lua.LTNil {
|
|
return nil, true, nil
|
|
}
|
|
|
|
s := ret.String()
|
|
return &s, true, nil
|
|
}
|
|
|
|
func callCommandHook(L *lua.LState, name, line string) (string, bool, bool, error) {
|
|
fn := L.GetGlobal(name)
|
|
if fn.Type() == lua.LTNil {
|
|
return "", true, false, nil
|
|
}
|
|
|
|
if err := L.CallByParam(lua.P{Fn: fn, NRet: 2, Protect: true}, lua.LString(line)); err != nil {
|
|
return "", true, true, err
|
|
}
|
|
|
|
allowVal := L.Get(-1)
|
|
lineVal := L.Get(-2)
|
|
L.Pop(2)
|
|
|
|
allow := true
|
|
if allowVal.Type() == lua.LTBool {
|
|
allow = lua.LVAsBool(allowVal)
|
|
}
|
|
|
|
next := ""
|
|
if lineVal.Type() != lua.LTNil {
|
|
next = lineVal.String()
|
|
}
|
|
|
|
return next, allow, true, nil
|
|
}
|