177 lines
4.8 KiB
Go
177 lines
4.8 KiB
Go
package gui
|
||
|
||
import (
|
||
"os/exec"
|
||
"runtime"
|
||
"stream-bot/internal/logger"
|
||
"time"
|
||
|
||
"github.com/lxn/walk"
|
||
. "github.com/lxn/walk/declarative"
|
||
)
|
||
|
||
const appVersion = "11.0.43"
|
||
|
||
func Run(webURL string,
|
||
getChatStatus func() bool,
|
||
getEventSubStatus func() (connected bool, subscriptions []string)) {
|
||
|
||
if runtime.GOOS != "windows" {
|
||
logger.Warn("GUI only supported on Windows, running without window")
|
||
return
|
||
}
|
||
|
||
var urlLE *walk.LineEdit
|
||
var openBtn *walk.PushButton
|
||
var exitBtn *walk.PushButton
|
||
var chatStatusLabel, eventSubLabel *walk.Label
|
||
|
||
var mw *walk.MainWindow
|
||
exitWithoutMinimize := false // флаг для полного закрытия
|
||
|
||
err := MainWindow{
|
||
AssignTo: &mw,
|
||
Title: "TTW_Bot v" + appVersion,
|
||
MinSize: Size{Width: 450, Height: 280},
|
||
Size: Size{Width: 500, Height: 300},
|
||
Layout: VBox{},
|
||
Children: []Widget{
|
||
Label{Text: "Веб-интерфейс бота доступен по адресу:"},
|
||
LineEdit{
|
||
AssignTo: &urlLE,
|
||
Text: webURL,
|
||
ReadOnly: true,
|
||
},
|
||
PushButton{
|
||
AssignTo: &openBtn,
|
||
Text: "🌐 Открыть в браузере",
|
||
OnClicked: func() {
|
||
openBrowser(webURL)
|
||
},
|
||
},
|
||
Label{Text: "─────────────────────────────────"},
|
||
Label{Text: "Статус Twitch чата:"},
|
||
Label{AssignTo: &chatStatusLabel, Text: "загрузка..."},
|
||
Label{Text: "Статус EventSub:"},
|
||
Label{AssignTo: &eventSubLabel, Text: "загрузка..."},
|
||
Label{Text: "─────────────────────────────────"},
|
||
PushButton{
|
||
AssignTo: &exitBtn,
|
||
Text: "❌ Завершить работу бота",
|
||
OnClicked: func() {
|
||
exitWithoutMinimize = true // запоминаем, что хотим выйти
|
||
_ = mw.Close()
|
||
},
|
||
},
|
||
},
|
||
}.Create()
|
||
|
||
if err != nil {
|
||
msg := "Failed to create GUI window: " + err.Error()
|
||
logger.Error(msg)
|
||
walk.MsgBox(nil, "Ошибка", msg, walk.MsgBoxIconError)
|
||
return
|
||
}
|
||
|
||
// При закрытии окна – либо сворачиваем в трей, либо завершаем программу
|
||
mw.Closing().Attach(func(cancel *bool, reason walk.CloseReason) {
|
||
if exitWithoutMinimize {
|
||
// полное завершение – не отменяем закрытие
|
||
return
|
||
}
|
||
// иначе – сворачиваем в трей
|
||
*cancel = true
|
||
mw.Hide()
|
||
})
|
||
|
||
// Создаём иконку в трее
|
||
ni, err := walk.NewNotifyIcon(mw)
|
||
if err != nil {
|
||
logger.Error("Failed to create notify icon: %v", err)
|
||
} else {
|
||
_ = ni.SetToolTip("TTW_Bot")
|
||
// Устанавливаем иконку (системная иконка информации)
|
||
if err := ni.SetIcon(walk.IconInformation()); err != nil {
|
||
logger.Warn("Failed to set icon: %v", err)
|
||
}
|
||
|
||
showAction := walk.NewAction()
|
||
_ = showAction.SetText("Показать окно")
|
||
showAction.Triggered().Attach(func() {
|
||
mw.Show()
|
||
mw.SetVisible(true)
|
||
})
|
||
exitAction := walk.NewAction()
|
||
_ = exitAction.SetText("Выход")
|
||
exitAction.Triggered().Attach(func() {
|
||
exitWithoutMinimize = true
|
||
_ = mw.Close()
|
||
})
|
||
menu := ni.ContextMenu()
|
||
_ = menu.Actions().Add(showAction)
|
||
_ = menu.Actions().Add(exitAction)
|
||
|
||
ni.MouseDown().Attach(func(x, y int, button walk.MouseButton) {
|
||
if button == walk.LeftButton {
|
||
mw.Show()
|
||
mw.SetVisible(true)
|
||
}
|
||
})
|
||
if err := ni.SetVisible(true); err != nil {
|
||
logger.Error("Failed to set tray icon visible: %v", err)
|
||
}
|
||
}
|
||
|
||
// Запускаем периодическое обновление статусов
|
||
ticker := time.NewTicker(5 * time.Second)
|
||
go func() {
|
||
for range ticker.C {
|
||
mw.Synchronize(func() {
|
||
chatConnected := getChatStatus()
|
||
if chatConnected {
|
||
_ = chatStatusLabel.SetText("✅ Подключен")
|
||
} else {
|
||
_ = chatStatusLabel.SetText("❌ Отключен")
|
||
}
|
||
esConnected, subs := getEventSubStatus()
|
||
if esConnected {
|
||
_ = eventSubLabel.SetText("✅ Подключен (" + itoa(len(subs)) + " подписок)")
|
||
} else {
|
||
_ = eventSubLabel.SetText("❌ Отключен")
|
||
}
|
||
})
|
||
}
|
||
}()
|
||
|
||
mw.Run()
|
||
ticker.Stop()
|
||
}
|
||
|
||
func openBrowser(url string) {
|
||
var cmd *exec.Cmd
|
||
switch runtime.GOOS {
|
||
case "windows":
|
||
cmd = exec.Command("cmd", "/c", "start", url)
|
||
case "darwin":
|
||
cmd = exec.Command("open", url)
|
||
default:
|
||
cmd = exec.Command("xdg-open", url)
|
||
}
|
||
if err := cmd.Start(); err != nil {
|
||
logger.Error("Failed to open browser: %v", err)
|
||
walk.MsgBox(nil, "Ошибка", "Не удалось открыть браузер: "+err.Error(), walk.MsgBoxIconError)
|
||
}
|
||
}
|
||
|
||
func itoa(i int) string {
|
||
if i == 0 {
|
||
return "0"
|
||
}
|
||
digits := ""
|
||
for i > 0 {
|
||
digits = string(rune('0'+i%10)) + digits
|
||
i /= 10
|
||
}
|
||
return digits
|
||
}
|