refactor: extract internal/config and eliminate global config var

Move Config struct to internal/config with exported fields. Replace
global var config with package-level cfg pointer. Add OpenLogFile to
config package. Add type alias Config = appconfig.Config in main
package for backward compatibility.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
JiXieShi
2026-05-23 21:45:08 +08:00
parent 2ce672cdde
commit 31dd9da490
15 changed files with 198 additions and 189 deletions
+14 -13
View File
@@ -11,6 +11,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
appconfig "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config"
"github.com/jixishi/SerialTerminalForWindowsTerminal/internal/event" "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/event"
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/charset" "github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/charset"
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward" "github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward"
@@ -35,7 +36,7 @@ type App struct {
} }
func NewApp(cfg *Config) (*App, error) { func NewApp(cfg *Config) (*App, error) {
f, err := openLogFile() f, err := appconfig.OpenLogFile(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -168,16 +169,16 @@ func (a *App) waitDone() <-chan struct{} {
} }
func (a *App) loadConfiguredForwards() { func (a *App) loadConfiguredForwards() {
for i, mode := range config.forWard { for i, mode := range a.cfg.ForWard {
m := forward.Mode(mode) m := forward.Mode(mode)
if m == forward.None { if m == forward.None {
continue continue
} }
if i >= len(config.address) { if i >= len(a.cfg.Address) {
a.Notifyf("[forward] skip #%d: missing address", i) a.Notifyf("[forward] skip #%d: missing address", i)
continue continue
} }
addr := strings.TrimSpace(config.address[i]) addr := strings.TrimSpace(a.cfg.Address[i])
if addr == "" { if addr == "" {
continue continue
} }
@@ -192,12 +193,12 @@ func (a *App) reportForwardIngress(id int, chunk []byte) {
return return
} }
if strings.EqualFold(a.cfg.inputCode, "hex") { if strings.EqualFold(a.cfg.InputCode, "hex") {
a.Notifyf("[forward#%d -> serial] % X\n", id, chunk) a.Notifyf("[forward#%d -> serial] % X\n", id, chunk)
return return
} }
converted, err := charset.ConvertChunk(chunk, a.cfg.inputCode, a.cfg.outputCode) converted, err := charset.ConvertChunk(chunk, a.cfg.InputCode, a.cfg.OutputCode)
if err != nil { if err != nil {
converted = bytes.Clone(chunk) converted = bytes.Clone(chunk)
} }
@@ -236,7 +237,7 @@ func (a *App) sendLine(line string) error {
return nil return nil
} }
payload := append([]byte(line), []byte(a.cfg.endStr)...) payload := append([]byte(line), []byte(a.cfg.EndStr)...)
return a.writeToSession(payload) return a.writeToSession(payload)
} }
@@ -283,7 +284,7 @@ func (a *App) handleLine(line string) {
} }
func (a *App) startOutputLoop() { func (a *App) startOutputLoop() {
if strings.EqualFold(a.cfg.inputCode, "hex") { if strings.EqualFold(a.cfg.InputCode, "hex") {
go a.readHexOutput() go a.readHexOutput()
return return
} }
@@ -292,7 +293,7 @@ func (a *App) startOutputLoop() {
} }
func (a *App) readHexOutput() { func (a *App) readHexOutput() {
frameSize := a.cfg.frameSize frameSize := a.cfg.FrameSize
if frameSize <= 0 { if frameSize <= 0 {
frameSize = 16 frameSize = 16
} }
@@ -312,7 +313,7 @@ func (a *App) readHexOutput() {
if len(outChunk) == 0 { if len(outChunk) == 0 {
continue continue
} }
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: charset.FormatHexFrame(outChunk, a.cfg.timesTamp, a.cfg.timesFmt)}) a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: charset.FormatHexFrame(outChunk, a.cfg.TimesTamp, a.cfg.TimesFmt)})
} }
if err != nil { if err != nil {
if err != io.EOF { if err != io.EOF {
@@ -347,15 +348,15 @@ func (a *App) readTextOutput() {
continue continue
} }
converted, convErr := charset.ConvertChunk(outChunk, a.cfg.inputCode, a.cfg.outputCode) converted, convErr := charset.ConvertChunk(outChunk, a.cfg.InputCode, a.cfg.OutputCode)
if convErr != nil { if convErr != nil {
a.Notifyf("[output] convert failed: %v", convErr) a.Notifyf("[output] convert failed: %v", convErr)
converted = bytes.Clone(outChunk) converted = bytes.Clone(outChunk)
} }
text := string(converted) text := string(converted)
if a.cfg.timesTamp { if a.cfg.TimesTamp {
text = prefixLines(text, time.Now().Format(a.cfg.timesFmt)+" ") text = prefixLines(text, time.Now().Format(a.cfg.TimesFmt)+" ")
} }
a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: text}) a.emit(event.UIEvent{Kind: event.UIEventOutput, Text: text})
} }
+8 -11
View File
@@ -61,7 +61,7 @@ func TestAppUIEvents(t *testing.T) {
func TestSendLine(t *testing.T) { func TestSendLine(t *testing.T) {
setupTestPipes() setupTestPipes()
a := &App{ a := &App{
cfg: &Config{endStr: "\r\n"}, cfg: &Config{EndStr: "\r\n"},
plugins: luaplugin.NewManager(), plugins: luaplugin.NewManager(),
uiEvents: make(chan event.UIEvent, 8), uiEvents: make(chan event.UIEvent, 8),
done: make(chan struct{}), done: make(chan struct{}),
@@ -83,7 +83,7 @@ func TestSendLine(t *testing.T) {
func TestHandleLine(t *testing.T) { func TestHandleLine(t *testing.T) {
setupTestPipes() setupTestPipes()
a := &App{ a := &App{
cfg: &Config{endStr: "\n", inputCode: "UTF-8", outputCode: "UTF-8"}, cfg: &Config{EndStr: "\n", InputCode: "UTF-8", OutputCode: "UTF-8"},
plugins: luaplugin.NewManager(), plugins: luaplugin.NewManager(),
uiEvents: make(chan event.UIEvent, 8), uiEvents: make(chan event.UIEvent, 8),
done: make(chan struct{}), done: make(chan struct{}),
@@ -159,22 +159,19 @@ func TestAppClose(t *testing.T) {
} }
func TestLoadConfiguredForwards(t *testing.T) { func TestLoadConfiguredForwards(t *testing.T) {
oldCfg := config
defer func() { config = oldCfg }()
listener, err := net.Listen("tcp", "127.0.0.1:0") listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatalf("listen failed: %v", err) t.Fatalf("listen failed: %v", err)
} }
defer listener.Close() defer listener.Close()
config = Config{ testCfg := &Config{
forWard: []int{int(forward.TCP), int(forward.None), int(forward.UDP)}, ForWard: []int{int(forward.TCP), int(forward.None), int(forward.UDP)},
address: []string{listener.Addr().String(), "", ""}, Address: []string{listener.Addr().String(), "", ""},
} }
a := &App{ a := &App{
cfg: &config, cfg: testCfg,
forward: forward.NewManager(func([]byte) error { return nil }, func(string, ...any) {}), forward: forward.NewManager(func([]byte) error { return nil }, func(string, ...any) {}),
uiEvents: make(chan event.UIEvent, 8), uiEvents: make(chan event.UIEvent, 8),
done: make(chan struct{}), done: make(chan struct{}),
@@ -191,7 +188,7 @@ func TestLoadConfiguredForwards(t *testing.T) {
func TestReportForwardIngress(t *testing.T) { func TestReportForwardIngress(t *testing.T) {
a := &App{ a := &App{
cfg: &Config{inputCode: "UTF-8", outputCode: "UTF-8"}, cfg: &Config{InputCode: "UTF-8", OutputCode: "UTF-8"},
uiEvents: make(chan event.UIEvent, 4), uiEvents: make(chan event.UIEvent, 4),
} }
a.SetUIEnabled(true) a.SetUIEnabled(true)
@@ -199,7 +196,7 @@ func TestReportForwardIngress(t *testing.T) {
a.reportForwardIngress(1, []byte("test")) a.reportForwardIngress(1, []byte("test"))
// Hex mode // Hex mode
a.cfg.inputCode = "hex" a.cfg.InputCode = "hex"
a.reportForwardIngress(2, []byte{0x41, 0x42}) a.reportForwardIngress(2, []byte{0x41, 0x42})
// Empty chunk // Empty chunk
+13 -13
View File
@@ -414,13 +414,13 @@ func (d *CommandDispatcher) handleModeCommand(args []string) error {
} }
d.app.Notifyf("[mode] input=%s output=%s end=%q hex=%v frame=%d timestamp=%v timefmt=%q forwardTargets=%d plugins=%d", d.app.Notifyf("[mode] input=%s output=%s end=%q hex=%v frame=%d timestamp=%v timefmt=%q forwardTargets=%d plugins=%d",
d.app.cfg.inputCode, d.app.cfg.InputCode,
d.app.cfg.outputCode, d.app.cfg.OutputCode,
d.app.cfg.endStr, d.app.cfg.EndStr,
strings.EqualFold(d.app.cfg.inputCode, "hex"), strings.EqualFold(d.app.cfg.InputCode, "hex"),
d.app.cfg.frameSize, d.app.cfg.FrameSize,
d.app.cfg.timesTamp, d.app.cfg.TimesTamp,
d.app.cfg.timesFmt, d.app.cfg.TimesFmt,
len(d.app.forward.List()), len(d.app.forward.List()),
len(d.app.plugins.List()), len(d.app.plugins.List()),
) )
@@ -439,25 +439,25 @@ func (d *CommandDispatcher) handleModeCommand(args []string) error {
switch field { switch field {
case "in": case "in":
d.app.cfg.inputCode = value d.app.cfg.InputCode = value
case "out": case "out":
d.app.cfg.outputCode = value d.app.cfg.OutputCode = value
case "end": case "end":
d.app.cfg.endStr = value d.app.cfg.EndStr = value
case "frame": case "frame":
n, err := strconv.Atoi(value) n, err := strconv.Atoi(value)
if err != nil || n <= 0 { if err != nil || n <= 0 {
return fmt.Errorf("frame must be a positive integer") return fmt.Errorf("frame must be a positive integer")
} }
d.app.cfg.frameSize = n d.app.cfg.FrameSize = n
case "timestamp": case "timestamp":
enabled, ok := parseOnOff(value) enabled, ok := parseOnOff(value)
if !ok { if !ok {
return fmt.Errorf("timestamp value must be on/off") return fmt.Errorf("timestamp value must be on/off")
} }
d.app.cfg.timesTamp = enabled d.app.cfg.TimesTamp = enabled
case "timefmt": case "timefmt":
d.app.cfg.timesFmt = value d.app.cfg.TimesFmt = value
default: default:
return fmt.Errorf("unknown mode field: %s", field) return fmt.Errorf("unknown mode field: %s", field)
} }
+12 -12
View File
@@ -26,7 +26,7 @@ func setupTestPipes() {
func newTestAppForCommand() *App { func newTestAppForCommand() *App {
a := &App{ a := &App{
cfg: &Config{inputCode: "UTF-8", outputCode: "UTF-8", endStr: "\n"}, cfg: &Config{InputCode: "UTF-8", OutputCode: "UTF-8", EndStr: "\n"},
plugins: luaplugin.NewManager(), plugins: luaplugin.NewManager(),
uiEvents: make(chan event.UIEvent, 32), uiEvents: make(chan event.UIEvent, 32),
done: make(chan struct{}), done: make(chan struct{}),
@@ -149,15 +149,15 @@ func TestCommandExecuteModeSet(t *testing.T) {
if err != nil || !handled { if err != nil || !handled {
t.Fatalf(".mode set end failed handled=%v err=%v", handled, err) t.Fatalf(".mode set end failed handled=%v err=%v", handled, err)
} }
if a.cfg.endStr != "\\r\\n" { if a.cfg.EndStr != "\\r\\n" {
t.Fatalf("mode set end not applied, got=%q", a.cfg.endStr) t.Fatalf("mode set end not applied, got=%q", a.cfg.EndStr)
} }
handled, err = a.dispatcher.Execute(".mode set timestamp on") handled, err = a.dispatcher.Execute(".mode set timestamp on")
if err != nil || !handled { if err != nil || !handled {
t.Fatalf(".mode set timestamp failed handled=%v err=%v", handled, err) t.Fatalf(".mode set timestamp failed handled=%v err=%v", handled, err)
} }
if !a.cfg.timesTamp { if !a.cfg.TimesTamp {
t.Fatalf("mode set timestamp should enable timesTamp") t.Fatalf("mode set timestamp should enable timesTamp")
} }
} }
@@ -298,32 +298,32 @@ func TestCommandExecuteModeSetAll(t *testing.T) {
if err != nil || !handled { if err != nil || !handled {
t.Fatalf(".mode set frame failed: handled=%v err=%v", handled, err) t.Fatalf(".mode set frame failed: handled=%v err=%v", handled, err)
} }
if a.cfg.frameSize != 32 { if a.cfg.FrameSize != 32 {
t.Fatalf("frameSize not set, got=%d", a.cfg.frameSize) t.Fatalf("frameSize not set, got=%d", a.cfg.FrameSize)
} }
handled, err = a.dispatcher.Execute(".mode set timefmt 2006") handled, err = a.dispatcher.Execute(".mode set timefmt 2006")
if err != nil || !handled { if err != nil || !handled {
t.Fatalf(".mode set timefmt failed: handled=%v err=%v", handled, err) t.Fatalf(".mode set timefmt failed: handled=%v err=%v", handled, err)
} }
if a.cfg.timesFmt != "2006" { if a.cfg.TimesFmt != "2006" {
t.Fatalf("timesFmt not set, got=%q", a.cfg.timesFmt) t.Fatalf("timesFmt not set, got=%q", a.cfg.TimesFmt)
} }
handled, err = a.dispatcher.Execute(".mode set out GBK") handled, err = a.dispatcher.Execute(".mode set out GBK")
if err != nil || !handled { if err != nil || !handled {
t.Fatalf(".mode set out failed: handled=%v err=%v", handled, err) t.Fatalf(".mode set out failed: handled=%v err=%v", handled, err)
} }
if a.cfg.outputCode != "GBK" { if a.cfg.OutputCode != "GBK" {
t.Fatalf("outputCode not set, got=%q", a.cfg.outputCode) t.Fatalf("outputCode not set, got=%q", a.cfg.OutputCode)
} }
handled, err = a.dispatcher.Execute(".mode set in GBK") handled, err = a.dispatcher.Execute(".mode set in GBK")
if err != nil || !handled { if err != nil || !handled {
t.Fatalf(".mode set in failed: handled=%v err=%v", handled, err) t.Fatalf(".mode set in failed: handled=%v err=%v", handled, err)
} }
if a.cfg.inputCode != "GBK" { if a.cfg.InputCode != "GBK" {
t.Fatalf("inputCode not set, got=%q", a.cfg.inputCode) t.Fatalf("inputCode not set, got=%q", a.cfg.InputCode)
} }
} }
+4 -36
View File
@@ -1,42 +1,10 @@
package main package main
import ( import (
"fmt" appconfig "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config"
"os"
"time"
) )
type Config struct { // Config is an alias for appconfig.Config to keep main-package code concise.
portName string type Config = appconfig.Config
baudRate int
dataBits int
stopBits int
parityBit int
outputCode string
inputCode string
endStr string
enableLog bool
logFilePath string
forWard []int
frameSize int
timesTamp bool
timesFmt string
address []string
enableGUI bool
hotkeyMod string
}
var config Config var cfg = &Config{}
func openLogFile() (*os.File, error) {
if config.enableLog {
path := fmt.Sprintf(config.logFilePath, config.portName, time.Now().Format("2006_01_02T150405"))
f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
return f, nil
}
return nil, nil
}
+31 -30
View File
@@ -7,6 +7,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
appconfig "github.com/jixishi/SerialTerminalForWindowsTerminal/internal/config"
"github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward" "github.com/jixishi/SerialTerminalForWindowsTerminal/pkg/forward"
) )
@@ -55,16 +56,16 @@ func TestParseForwardMode(t *testing.T) {
} }
func TestOpenLogFile(t *testing.T) { func TestOpenLogFile(t *testing.T) {
old := config old := *cfg
defer func() { config = old }() defer func() { *cfg = old }()
config = Config{ *cfg = Config{
enableLog: true, EnableLog: true,
portName: "COM1", PortName: "COM1",
logFilePath: filepath.Join(t.TempDir(), "%s-%s.log"), LogFilePath: filepath.Join(t.TempDir(), "%s-%s.log"),
} }
f, err := openLogFile() f, err := appconfig.OpenLogFile(cfg)
if err != nil { if err != nil {
t.Fatalf("openLogFile() unexpected error: %v", err) t.Fatalf("openLogFile() unexpected error: %v", err)
} }
@@ -73,8 +74,8 @@ func TestOpenLogFile(t *testing.T) {
} }
_ = f.Close() _ = f.Close()
config.enableLog = false cfg.EnableLog = false
f, err = openLogFile() f, err = appconfig.OpenLogFile(cfg)
if err != nil { if err != nil {
t.Fatalf("openLogFile() unexpected error with enableLog=false: %v", err) t.Fatalf("openLogFile() unexpected error with enableLog=false: %v", err)
} }
@@ -114,55 +115,55 @@ func TestFlagFindValue(t *testing.T) {
} }
func TestFlagExt(t *testing.T) { func TestFlagExt(t *testing.T) {
old := config old := *cfg
defer func() { config = old }() defer func() { *cfg = old }()
config = Config{} *cfg = Config{}
flagExt() flagExt()
if config.enableLog { if cfg.EnableLog {
t.Fatalf("expected enableLog=false when logFilePath empty") t.Fatalf("expected enableLog=false when logFilePath empty")
} }
if config.timesTamp { if cfg.TimesTamp {
t.Fatalf("expected timesTamp=false when timesFmt empty") t.Fatalf("expected timesTamp=false when timesFmt empty")
} }
if config.hotkeyMod != "ctrl+alt" { if cfg.HotkeyMod != "ctrl+alt" {
t.Fatalf("expected default hotkeyMod=ctrl+alt, got=%q", config.hotkeyMod) t.Fatalf("expected default hotkeyMod=ctrl+alt, got=%q", cfg.HotkeyMod)
} }
config = Config{logFilePath: "/tmp/log.txt"} *cfg = Config{LogFilePath: "/tmp/log.txt"}
flagExt() flagExt()
if !config.enableLog { if !cfg.EnableLog {
t.Fatalf("expected enableLog=true when logFilePath set") t.Fatalf("expected enableLog=true when logFilePath set")
} }
config = Config{timesFmt: "2006-01-02"} *cfg = Config{TimesFmt: "2006-01-02"}
flagExt() flagExt()
if !config.timesTamp { if !cfg.TimesTamp {
t.Fatalf("expected timesTamp=true when timesFmt set") t.Fatalf("expected timesTamp=true when timesFmt set")
} }
config = Config{hotkeyMod: ""} *cfg = Config{HotkeyMod: ""}
flagExt() flagExt()
if config.hotkeyMod != "ctrl+alt" { if cfg.HotkeyMod != "ctrl+alt" {
t.Fatalf("empty hotkeyMod should default to ctrl+alt") t.Fatalf("empty hotkeyMod should default to ctrl+alt")
} }
config = Config{hotkeyMod: "ctrl+shift"} *cfg = Config{HotkeyMod: "ctrl+shift"}
flagExt() flagExt()
if config.hotkeyMod != "ctrl+shift" { if cfg.HotkeyMod != "ctrl+shift" {
t.Fatalf("expected ctrl+shift preserved") t.Fatalf("expected ctrl+shift preserved")
} }
config = Config{hotkeyMod: " CTRL+SHIFT "} *cfg = Config{HotkeyMod: " CTRL+SHIFT "}
flagExt() flagExt()
if config.hotkeyMod != "ctrl+shift" { if cfg.HotkeyMod != "ctrl+shift" {
t.Fatalf("expected whitespace+case normalization, got=%q", config.hotkeyMod) t.Fatalf("expected whitespace+case normalization, got=%q", cfg.HotkeyMod)
} }
config = Config{hotkeyMod: "invalid"} *cfg = Config{HotkeyMod: "invalid"}
flagExt() flagExt()
if config.hotkeyMod != "ctrl+alt" { if cfg.HotkeyMod != "ctrl+alt" {
t.Fatalf("invalid hotkeyMod should default to ctrl+alt, got=%q", config.hotkeyMod) t.Fatalf("invalid hotkeyMod should default to ctrl+alt, got=%q", cfg.HotkeyMod)
} }
} }
+5 -5
View File
@@ -60,10 +60,10 @@ func TestParseCSIu(t *testing.T) {
} }
func TestIsExitHotkeySeq(t *testing.T) { func TestIsExitHotkeySeq(t *testing.T) {
oldCfg := config oldCfg := *cfg
defer func() { config = oldCfg }() defer func() { *cfg = oldCfg }()
config = Config{hotkeyMod: "ctrl+alt"} *cfg = Config{HotkeyMod: "ctrl+alt"}
// CSI u Ctrl+Alt+C (mod=6) // CSI u Ctrl+Alt+C (mod=6)
if !isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '6', 'u'}) { if !isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '6', 'u'}) {
@@ -88,7 +88,7 @@ func TestIsExitHotkeySeq(t *testing.T) {
} }
// Switch to ctrl+shift // Switch to ctrl+shift
config = Config{hotkeyMod: "ctrl+shift"} *cfg = Config{HotkeyMod: "ctrl+shift"}
if !isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '5', 'u'}) { if !isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '5', 'u'}) {
t.Fatalf("Ctrl+Shift+C should exit with ctrl+shift config") t.Fatalf("Ctrl+Shift+C should exit with ctrl+shift config")
@@ -111,7 +111,7 @@ func TestIsExitHotkeySeq(t *testing.T) {
t.Fatalf("plain bytes should not exit") t.Fatalf("plain bytes should not exit")
} }
config = Config{hotkeyMod: "ctrl+alt"} *cfg = Config{HotkeyMod: "ctrl+alt"}
// Ctrl only (mod=4) should not exit (requires Alt too) // Ctrl only (mod=4) should not exit (requires Alt too)
if isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '4', 'u'}) { if isExitHotkeySeq([]byte{0x1b, '[', '9', '9', ';', '4', 'u'}) {
t.Fatalf("Ctrl+C (without Alt) should not exit") t.Fatalf("Ctrl+C (without Alt) should not exit")
+41 -41
View File
@@ -47,21 +47,21 @@ type Flag struct {
} }
var ( var (
portName = Flag{ptrVal{string: &config.portName}, "p", "port", Val{string: ""}, "要连接的串口\t(/dev/ttyUSB0、COMx)"} portName = Flag{ptrVal{string: &cfg.PortName}, "p", "port", Val{string: ""}, "要连接的串口\t(/dev/ttyUSB0、COMx)"}
baudRate = Flag{ptrVal{int: &config.baudRate}, "b", "baud", Val{int: 115200}, "波特率"} baudRate = Flag{ptrVal{int: &cfg.BaudRate}, "b", "baud", Val{int: 115200}, "波特率"}
dataBits = Flag{ptrVal{int: &config.dataBits}, "d", "data", Val{int: 8}, "数据位"} dataBits = Flag{ptrVal{int: &cfg.DataBits}, "d", "data", Val{int: 8}, "数据位"}
stopBits = Flag{ptrVal{int: &config.stopBits}, "s", "stop", Val{int: 0}, "停止位停止位(0: 1停止 1:1.5停止 2:2停止)"} stopBits = Flag{ptrVal{int: &cfg.StopBits}, "s", "stop", Val{int: 0}, "停止位停止位(0: 1停止 1:1.5停止 2:2停止)"}
outputCode = Flag{ptrVal{string: &config.outputCode}, "o", "out", Val{string: "UTF-8"}, "输出编码"} outputCode = Flag{ptrVal{string: &cfg.OutputCode}, "o", "out", Val{string: "UTF-8"}, "输出编码"}
inputCode = Flag{ptrVal{string: &config.inputCode}, "i", "in", Val{string: "UTF-8"}, "输入编码"} inputCode = Flag{ptrVal{string: &cfg.InputCode}, "i", "in", Val{string: "UTF-8"}, "输入编码"}
endStr = Flag{ptrVal{string: &config.endStr}, "e", "end", Val{string: "\n"}, "终端换行符"} endStr = Flag{ptrVal{string: &cfg.EndStr}, "e", "end", Val{string: "\n"}, "终端换行符"}
logExt = Flag{v: ptrVal{ext: &config.logFilePath}, sStr: "l", lStr: "log", dv: Val{extdef: "./%s-$s.txt", string: ""}, help: "日志保存路径"} logExt = Flag{v: ptrVal{ext: &cfg.LogFilePath}, sStr: "l", lStr: "log", dv: Val{extdef: "./%s-$s.txt", string: ""}, help: "日志保存路径"}
timeExt = Flag{v: ptrVal{ext: &config.timesFmt}, sStr: "t", lStr: "time", dv: Val{extdef: "[06-01-02 15:04:05.000]", string: ""}, help: "时间戳格式化字段"} timeExt = Flag{v: ptrVal{ext: &cfg.TimesFmt}, sStr: "t", lStr: "time", dv: Val{extdef: "[06-01-02 15:04:05.000]", string: ""}, help: "时间戳格式化字段"}
forWard = Flag{ptrVal{il: &config.forWard}, "f", "forward", Val{int: 0}, "转发模式(0: 无 1:TCP-C 2:UDP-C 支持多次传入)"} forWard = Flag{ptrVal{il: &cfg.ForWard}, "f", "forward", Val{int: 0}, "转发模式(0: 无 1:TCP-C 2:UDP-C 支持多次传入)"}
address = Flag{ptrVal{sl: &config.address}, "a", "address", Val{string: "127.0.0.1:12345"}, "转发服务地址(支持多次传入)"} address = Flag{ptrVal{sl: &cfg.Address}, "a", "address", Val{string: "127.0.0.1:12345"}, "转发服务地址(支持多次传入)"}
frameSize = Flag{ptrVal{int: &config.frameSize}, "F", "Frame", Val{int: 16}, "帧大小"} frameSize = Flag{ptrVal{int: &cfg.FrameSize}, "F", "Frame", Val{int: 16}, "帧大小"}
parityBit = Flag{ptrVal{int: &config.parityBit}, "v", "verify", Val{int: 0}, "奇偶校验(0:无校验、1:奇校验、2:偶校验、3:1校验、4:0校验)"} parityBit = Flag{ptrVal{int: &cfg.ParityBit}, "v", "verify", Val{int: 0}, "奇偶校验(0:无校验、1:奇校验、2:偶校验、3:1校验、4:0校验)"}
guiMode = Flag{ptrVal{bool: &config.enableGUI}, "g", "gui", Val{bool: false}, "启用TUI交互界面"} guiMode = Flag{ptrVal{bool: &cfg.EnableGUI}, "g", "gui", Val{bool: false}, "启用TUI交互界面"}
hotkeyMod = Flag{ptrVal{string: &config.hotkeyMod}, "k", "hotkey-mod", Val{string: "ctrl+alt"}, "本地快捷键修饰(ctrl+alt|ctrl+shift)"} hotkeyMod = Flag{ptrVal{string: &cfg.HotkeyMod}, "k", "hotkey-mod", Val{string: "ctrl+alt"}, "本地快捷键修饰(ctrl+alt|ctrl+shift)"}
flags = []Flag{portName, baudRate, dataBits, stopBits, outputCode, inputCode, endStr, forWard, address, frameSize, parityBit, logExt, timeExt, guiMode, hotkeyMod} flags = []Flag{portName, baudRate, dataBits, stopBits, outputCode, inputCode, endStr, forWard, address, frameSize, parityBit, logExt, timeExt, guiMode, hotkeyMod}
) )
@@ -182,18 +182,18 @@ func flagInit(f *Flag) {
} }
} }
func flagExt() { func flagExt() {
if config.logFilePath != "" { if cfg.LogFilePath != "" {
config.enableLog = true cfg.EnableLog = true
} }
if config.timesFmt != "" { if cfg.TimesFmt != "" {
config.timesTamp = true cfg.TimesTamp = true
} }
if config.hotkeyMod == "" { if cfg.HotkeyMod == "" {
config.hotkeyMod = "ctrl+alt" cfg.HotkeyMod = "ctrl+alt"
} }
config.hotkeyMod = strings.ToLower(strings.TrimSpace(config.hotkeyMod)) cfg.HotkeyMod = strings.ToLower(strings.TrimSpace(cfg.HotkeyMod))
if config.hotkeyMod != "ctrl+alt" && config.hotkeyMod != "ctrl+shift" { if cfg.HotkeyMod != "ctrl+alt" && cfg.HotkeyMod != "ctrl+shift" {
config.hotkeyMod = "ctrl+alt" cfg.HotkeyMod = "ctrl+alt"
} }
} }
func getCliFlag() { func getCliFlag() {
@@ -230,7 +230,7 @@ func getCliFlag() {
singleselect.WithPageSize(4), singleselect.WithPageSize(4),
singleselect.WithFilterInput(inputs), singleselect.WithFilterInput(inputs),
).Display("选择串口") ).Display("选择串口")
config.portName = ports[s] cfg.PortName = ports[s]
s, _ = inf.NewSingleSelect( s, _ = inf.NewSingleSelect(
bauds, bauds,
@@ -238,38 +238,38 @@ func getCliFlag() {
singleselect.WithPageSize(4), singleselect.WithPageSize(4),
).Display("选择波特率") ).Display("选择波特率")
if s != 0 { if s != 0 {
config.baudRate, _ = strconv.Atoi(bauds[s]) cfg.BaudRate, _ = strconv.Atoi(bauds[s])
} else { } else {
b, _ := inf.NewText( b, _ := inf.NewText(
text.WithPrompt("BaudRate:"), text.WithPrompt("BaudRate:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle), text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue("115200"), text.WithDefaultValue("115200"),
).Display() ).Display()
config.baudRate, _ = strconv.Atoi(b) cfg.BaudRate, _ = strconv.Atoi(b)
} }
v, _ := inf.NewConfirmWithSelection( v, _ := inf.NewConfirmWithSelection(
confirm.WithPrompt("启用Hex"), confirm.WithPrompt("启用Hex"),
).Display() ).Display()
if v { if v {
config.inputCode = "hex" cfg.InputCode = "hex"
b, _ := inf.NewText( b, _ := inf.NewText(
text.WithPrompt("Frames:"), text.WithPrompt("Frames:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle), text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue("16"), text.WithDefaultValue("16"),
).Display() ).Display()
config.frameSize, _ = strconv.Atoi(b) cfg.FrameSize, _ = strconv.Atoi(b)
} }
v, _ = inf.NewConfirmWithSelection( v, _ = inf.NewConfirmWithSelection(
confirm.WithPrompt("启用时间戳"), confirm.WithPrompt("启用时间戳"),
).Display() ).Display()
config.timesTamp = v cfg.TimesTamp = v
if v { if v {
b, _ := inf.NewText( b, _ := inf.NewText(
text.WithPrompt("格式化字段:"), text.WithPrompt("格式化字段:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle), text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(timeExt.dv.extdef), text.WithDefaultValue(timeExt.dv.extdef),
).Display() ).Display()
config.timesFmt = b cfg.TimesFmt = b
} }
v, _ = inf.NewConfirmWithSelection( v, _ = inf.NewConfirmWithSelection(
confirm.WithPrompt("启用高级配置"), confirm.WithPrompt("启用高级配置"),
@@ -281,7 +281,7 @@ func getCliFlag() {
singleselect.WithPageSize(4), singleselect.WithPageSize(4),
singleselect.WithFilterInput(inputs), singleselect.WithFilterInput(inputs),
).Display("选择数据位") ).Display("选择数据位")
config.dataBits, _ = strconv.Atoi(datas[s]) cfg.DataBits, _ = strconv.Atoi(datas[s])
s, _ = inf.NewSingleSelect( s, _ = inf.NewSingleSelect(
stops, stops,
@@ -289,7 +289,7 @@ func getCliFlag() {
singleselect.WithPageSize(4), singleselect.WithPageSize(4),
singleselect.WithFilterInput(inputs), singleselect.WithFilterInput(inputs),
).Display("选择停止位") ).Display("选择停止位")
config.stopBits = s cfg.StopBits = s
s, _ = inf.NewSingleSelect( s, _ = inf.NewSingleSelect(
paritys, paritys,
@@ -297,14 +297,14 @@ func getCliFlag() {
singleselect.WithPageSize(4), singleselect.WithPageSize(4),
singleselect.WithFilterInput(inputs), singleselect.WithFilterInput(inputs),
).Display("选择校验位") ).Display("选择校验位")
config.parityBit = s cfg.ParityBit = s
t, _ := inf.NewText( t, _ := inf.NewText(
text.WithPrompt("换行符:"), text.WithPrompt("换行符:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle), text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(endStr.dv.string), text.WithDefaultValue(endStr.dv.string),
).Display() ).Display()
config.endStr = t cfg.EndStr = t
v, _ = inf.NewConfirmWithSelection( v, _ = inf.NewConfirmWithSelection(
confirm.WithDefaultYes(), confirm.WithDefaultYes(),
@@ -317,14 +317,14 @@ func getCliFlag() {
text.WithPromptStyle(theme.DefaultTheme.PromptStyle), text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(inputCode.dv.string), text.WithDefaultValue(inputCode.dv.string),
).Display() ).Display()
config.inputCode = t cfg.InputCode = t
t, _ = inf.NewText( t, _ = inf.NewText(
text.WithPrompt("输出编码:"), text.WithPrompt("输出编码:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle), text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(outputCode.dv.string), text.WithDefaultValue(outputCode.dv.string),
).Display() ).Display()
config.outputCode = t cfg.OutputCode = t
} }
G_F_mode: G_F_mode:
s, _ = inf.NewSingleSelect( s, _ = inf.NewSingleSelect(
@@ -334,13 +334,13 @@ func getCliFlag() {
singleselect.WithFilterInput(inputs), singleselect.WithFilterInput(inputs),
).Display("选择转发模式") ).Display("选择转发模式")
if s != 0 { if s != 0 {
config.forWard = append(config.forWard, s) cfg.ForWard = append(cfg.ForWard, s)
t, _ = inf.NewText( t, _ = inf.NewText(
text.WithPrompt("地址:"), text.WithPrompt("地址:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle), text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(address.dv.string), text.WithDefaultValue(address.dv.string),
).Display() ).Display()
config.address = append(config.address, t) cfg.Address = append(cfg.Address, t)
goto G_F_mode goto G_F_mode
} }
@@ -348,14 +348,14 @@ func getCliFlag() {
confirm.WithDefaultYes(), confirm.WithDefaultYes(),
confirm.WithPrompt("启用日志"), confirm.WithPrompt("启用日志"),
).Display() ).Display()
config.enableLog = e cfg.EnableLog = e
if e { if e {
t, _ = inf.NewText( t, _ = inf.NewText(
text.WithPrompt("Path:"), text.WithPrompt("Path:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle), text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue("./%s-$s.txt"), text.WithDefaultValue("./%s-$s.txt"),
).Display() ).Display()
config.logFilePath = t cfg.LogFilePath = t
} }
} }
+42
View File
@@ -0,0 +1,42 @@
// Package config holds the application configuration.
package config
import (
"fmt"
"os"
"time"
)
// Config holds all application settings.
type Config struct {
PortName string
BaudRate int
DataBits int
StopBits int
ParityBit int
OutputCode string
InputCode string
EndStr string
EnableLog bool
LogFilePath string
ForWard []int
FrameSize int
TimesTamp bool
TimesFmt string
Address []string
EnableGUI bool
HotkeyMod string
}
// OpenLogFile opens the configured log file for writing, or returns nil if logging is disabled.
func OpenLogFile(cfg *Config) (*os.File, error) {
if cfg.EnableLog {
path := fmt.Sprintf(cfg.LogFilePath, cfg.PortName, time.Now().Format("2006_01_02T150405"))
f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
return f, nil
}
return nil, nil
}
+8 -8
View File
@@ -32,10 +32,10 @@ func main() {
normalizeFlags() normalizeFlags()
pflag.Parse() pflag.Parse()
flagExt() flagExt()
if config.portName == "" { if cfg.PortName == "" {
getCliFlag() getCliFlag()
} }
ports, err := checkPortAvailability(config.portName) ports, err := checkPortAvailability(cfg.PortName)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
printUsage(ports) printUsage(ports)
@@ -52,7 +52,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
app, err := NewApp(&config) app, err := NewApp(cfg)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "create app failed: %v\n", err) fmt.Fprintf(os.Stderr, "create app failed: %v\n", err)
os.Exit(1) os.Exit(1)
@@ -63,9 +63,9 @@ func main() {
app.startOutputLoop() app.startOutputLoop()
go forwardInterruptToRemote(app) go forwardInterruptToRemote(app)
app.SetUIEnabled(config.enableGUI) app.SetUIEnabled(cfg.EnableGUI)
if config.enableGUI { if cfg.EnableGUI {
model := newUIModel(app) model := newUIModel(app)
p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithoutSignalHandler()) p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithoutSignalHandler())
if _, err = p.Run(); err != nil { if _, err = p.Run(); err != nil {
@@ -287,7 +287,7 @@ func runConsole(app *App) error {
} }
if b == '\r' || b == '\n' { if b == '\r' || b == '\n' {
if err = app.writeToSession([]byte(config.endStr)); err != nil { if err = app.writeToSession([]byte(cfg.EndStr)); err != nil {
app.Statusf("[send] %v", err) app.Statusf("[send] %v", err)
} }
lineStart = true lineStart = true
@@ -328,7 +328,7 @@ func parseCSIu(seq []byte) (cp int, mod int, ok bool) {
} }
func isAltKeyExit(b byte) bool { func isAltKeyExit(b byte) bool {
if normalizeHotkeyPrefix(config.hotkeyMod) != "ctrl+alt" { if normalizeHotkeyPrefix(cfg.HotkeyMod) != "ctrl+alt" {
return false return false
} }
// 0x2E = scan code for 'C', 0x03 = Ctrl+C, 0x63 = 'c', 0x43 = 'C' // 0x2E = scan code for 'C', 0x03 = Ctrl+C, 0x63 = 'c', 0x43 = 'C'
@@ -336,7 +336,7 @@ func isAltKeyExit(b byte) bool {
} }
func isExitHotkeySeq(seq []byte) bool { func isExitHotkeySeq(seq []byte) bool {
mod := normalizeHotkeyPrefix(config.hotkeyMod) mod := normalizeHotkeyPrefix(cfg.HotkeyMod)
// CSI u format: ESC [ codepoint ; modifier u // CSI u format: ESC [ codepoint ; modifier u
// Only matches when the Ctrl modifier bit (4) is present, // Only matches when the Ctrl modifier bit (4) is present,
+2 -2
View File
@@ -9,7 +9,7 @@ import (
func handleLocalHotkey(m *uiModel, key string) bool { func handleLocalHotkey(m *uiModel, key string) bool {
if m.isLocalHotkey(key, "h") { if m.isLocalHotkey(key, "h") {
modifier := strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.hotkeyMod)) modifier := strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.HotkeyMod))
m.app.ShowModal("Shortcuts", modifier+"+C => local exit\nCtrl+C => remote interrupt\n"+modifier+"+F => forward panel\n"+modifier+"+P => plugin panel\n"+modifier+"+M => mode panel\nF1 => shortcut help") m.app.ShowModal("Shortcuts", modifier+"+C => local exit\nCtrl+C => remote interrupt\n"+modifier+"+F => forward panel\n"+modifier+"+P => plugin panel\n"+modifier+"+M => mode panel\nF1 => shortcut help")
return true return true
} }
@@ -48,7 +48,7 @@ func (m *uiModel) isLocalHotkey(key, action string) bool {
} }
} }
mod := normalizeHotkeyPrefix(m.app.cfg.hotkeyMod) mod := normalizeHotkeyPrefix(m.app.cfg.HotkeyMod)
if mod == "ctrl+shift" { if mod == "ctrl+shift" {
return hasCtrl && hasShift return hasCtrl && hasShift
} }
+4 -4
View File
@@ -160,7 +160,7 @@ func (m *uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
if m.isLocalHotkey(keyStr, "c") { if m.isLocalHotkey(keyStr, "c") {
m.app.Statusf("[local] exiting by %s+C", strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.hotkeyMod))) m.app.Statusf("[local] exiting by %s+C", strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.HotkeyMod)))
m.app.Close() m.app.Close()
return m, tea.Quit return m, tea.Quit
} }
@@ -171,7 +171,7 @@ func (m *uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Some terminals can't encode Ctrl+Alt/Shift+H distinctly and report Ctrl+H. // Some terminals can't encode Ctrl+Alt/Shift+H distinctly and report Ctrl+H.
if keyStr == "ctrl+h" { if keyStr == "ctrl+h" {
handleLocalHotkey(m, hotkeyWith(m.app.cfg.hotkeyMod, "h")) handleLocalHotkey(m, hotkeyWith(m.app.cfg.HotkeyMod, "h"))
return m, nil return m, nil
} }
@@ -184,7 +184,7 @@ func (m *uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch keyStr { switch keyStr {
case "f1": case "f1":
handleLocalHotkey(m, hotkeyWith(m.app.cfg.hotkeyMod, "h")) handleLocalHotkey(m, hotkeyWith(m.app.cfg.HotkeyMod, "h"))
return m, nil return m, nil
case "tab", "shift+tab": case "tab", "shift+tab":
@@ -245,7 +245,7 @@ func (m *uiModel) View() string {
} else if len(m.suggestions) == 1 { } else if len(m.suggestions) == 1 {
suggest = "Tab: " + m.suggestions[0] suggest = "Tab: " + m.suggestions[0]
} }
modifier := strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.hotkeyMod)) modifier := strings.ToUpper(normalizeHotkeyPrefix(m.app.cfg.HotkeyMod))
hotkeys := "Hotkeys: Ctrl+C remote | " + modifier + "+C local | " + modifier + "+F forward | " + modifier + "+P plugins | " + modifier + "+M mode | F1 help" hotkeys := "Hotkeys: Ctrl+C remote | " + modifier + "+C local | " + modifier + "+F forward | " + modifier + "+P plugins | " + modifier + "+M mode | F1 help"
hotkeys = lipgloss.NewStyle().Faint(true).Foreground(lipgloss.Color("245")).Render(hotkeys) hotkeys = lipgloss.NewStyle().Faint(true).Foreground(lipgloss.Color("245")).Render(hotkeys)
status := m.statusLine status := m.statusLine
+2 -2
View File
@@ -71,7 +71,7 @@ func (m *uiModel) refreshPanel() {
} }
func (m *uiModel) buildModeItems() []modeItem { func (m *uiModel) buildModeItems() []modeItem {
return []modeItem{{"in", "Input Charset", m.app.cfg.inputCode}, {"out", "Output Charset", m.app.cfg.outputCode}, {"end", "Line End", fmt.Sprintf("%q", m.app.cfg.endStr)}, {"frame", "Hex Frame Size", fmt.Sprintf("%d", m.app.cfg.frameSize)}, {"timestamp", "Timestamp", fmt.Sprintf("%v", m.app.cfg.timesTamp)}, {"timefmt", "Timestamp Format", m.app.cfg.timesFmt}} return []modeItem{{"in", "Input Charset", m.app.cfg.InputCode}, {"out", "Output Charset", m.app.cfg.OutputCode}, {"end", "Line End", fmt.Sprintf("%q", m.app.cfg.EndStr)}, {"frame", "Hex Frame Size", fmt.Sprintf("%d", m.app.cfg.FrameSize)}, {"timestamp", "Timestamp", fmt.Sprintf("%v", m.app.cfg.TimesTamp)}, {"timefmt", "Timestamp Format", m.app.cfg.TimesFmt}}
} }
func (m *uiModel) handleForwardPanelKey(key string) bool { func (m *uiModel) handleForwardPanelKey(key string) bool {
@@ -213,7 +213,7 @@ func (m *uiModel) handleModePanelKey(key string) bool {
switch key { switch key {
case " ": case " ":
if sel.key == "timestamp" { if sel.key == "timestamp" {
if m.app.cfg.timesTamp { if m.app.cfg.TimesTamp {
m.app.handleLine(".mode set timestamp off") m.app.handleLine(".mode set timestamp off")
} else { } else {
m.app.handleLine(".mode set timestamp on") m.app.handleLine(".mode set timestamp on")
+7 -7
View File
@@ -42,7 +42,7 @@ func TestRenderModal(t *testing.T) {
} }
func TestHandleCtrlShiftLocalHelp(t *testing.T) { func TestHandleCtrlShiftLocalHelp(t *testing.T) {
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+alt"}} a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+alt"}}
a.SetUIEnabled(true) a.SetUIEnabled(true)
m := uiModel{app: a} m := uiModel{app: a}
@@ -103,7 +103,7 @@ func TestIsLocalHotkeyAll(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
a := &App{cfg: &Config{hotkeyMod: tt.mod}} a := &App{cfg: &Config{HotkeyMod: tt.mod}}
m := uiModel{app: a} m := uiModel{app: a}
got := m.isLocalHotkey(tt.key, tt.action) got := m.isLocalHotkey(tt.key, tt.action)
if got != tt.want { if got != tt.want {
@@ -216,7 +216,7 @@ func TestMaxIntFunc(t *testing.T) {
} }
func TestHandleLocalHotkeyForward(t *testing.T) { func TestHandleLocalHotkeyForward(t *testing.T) {
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+alt"}} a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+alt"}}
a.SetUIEnabled(true) a.SetUIEnabled(true)
m := uiModel{app: a} m := uiModel{app: a}
@@ -230,7 +230,7 @@ func TestHandleLocalHotkeyForward(t *testing.T) {
} }
func TestHandleLocalHotkeyPlugin(t *testing.T) { func TestHandleLocalHotkeyPlugin(t *testing.T) {
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+alt"}} a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+alt"}}
a.SetUIEnabled(true) a.SetUIEnabled(true)
m := uiModel{app: a} m := uiModel{app: a}
@@ -244,7 +244,7 @@ func TestHandleLocalHotkeyPlugin(t *testing.T) {
} }
func TestHandleLocalHotkeyMode(t *testing.T) { func TestHandleLocalHotkeyMode(t *testing.T) {
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+alt"}} a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+alt"}}
a.SetUIEnabled(true) a.SetUIEnabled(true)
m := uiModel{app: a} m := uiModel{app: a}
@@ -258,7 +258,7 @@ func TestHandleLocalHotkeyMode(t *testing.T) {
} }
func TestHandleLocalHotkeyUnknown(t *testing.T) { func TestHandleLocalHotkeyUnknown(t *testing.T) {
a := &App{cfg: &Config{hotkeyMod: "ctrl+alt"}} a := &App{cfg: &Config{HotkeyMod: "ctrl+alt"}}
m := uiModel{app: a} m := uiModel{app: a}
if handleLocalHotkey(&m, "ctrl+alt+x") { if handleLocalHotkey(&m, "ctrl+alt+x") {
@@ -267,7 +267,7 @@ func TestHandleLocalHotkeyUnknown(t *testing.T) {
} }
func TestHandleLocalHotkeyCtrlShift(t *testing.T) { func TestHandleLocalHotkeyCtrlShift(t *testing.T) {
a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{hotkeyMod: "ctrl+shift"}} a := &App{uiEvents: make(chan event.UIEvent, 4), cfg: &Config{HotkeyMod: "ctrl+shift"}}
a.SetUIEnabled(true) a.SetUIEnabled(true)
m := uiModel{app: a} m := uiModel{app: a}
+5 -5
View File
@@ -34,13 +34,13 @@ func checkPortAvailability(name string) ([]string, error) {
func OpenSerial() error { func OpenSerial() error {
mode := &serial.Mode{ mode := &serial.Mode{
BaudRate: config.baudRate, BaudRate: cfg.BaudRate,
StopBits: serial.StopBits(config.stopBits), StopBits: serial.StopBits(cfg.StopBits),
DataBits: config.dataBits, DataBits: cfg.DataBits,
Parity: serial.Parity(config.parityBit), Parity: serial.Parity(cfg.ParityBit),
} }
var err error var err error
serialPort, err = serial.Open(config.portName, mode) serialPort, err = serial.Open(cfg.PortName, mode)
return err return err
} }