From ba8a143f9bef68ebe515882c2977c475116a82d5 Mon Sep 17 00:00:00 2001 From: Audrius Karabanovas Date: Fri, 25 Jun 2021 18:37:53 +0300 Subject: [PATCH 1/2] Enabling global shortcut for recording --- cmd/abyss-blackbox/main.go | 56 +++++++++++++++++++++++++++-- go.mod | 7 +++- go.sum | 12 +++---- internal/mainwindow/mainwindow.go | 58 ++++++++++++++++++++++++------- 4 files changed, 112 insertions(+), 21 deletions(-) diff --git a/cmd/abyss-blackbox/main.go b/cmd/abyss-blackbox/main.go index 6a787df..61a3c4e 100644 --- a/cmd/abyss-blackbox/main.go +++ b/cmd/abyss-blackbox/main.go @@ -38,6 +38,14 @@ type captureConfig struct { EVEClientWindowTitle string EVEGameLogsFolder string TestServer bool + RecorderShortcutText string + RecorderShortcut walk.Shortcut +} + +// SetRecorderShortcut satisfies ShortcutSetter interface. +func (c *captureConfig) SetRecorderShortcut(s walk.Shortcut) { + c.RecorderShortcut = s + c.RecorderShortcutText = s.String() } func main() { @@ -101,7 +109,7 @@ func main() { } }(notificationChannel, notificationIcon) - armw.RecordingButton.Clicked().Attach(func() { + recordingButtonHandler := func() { if recorder.Status() == RECORDER_STOPPED { charsChecked := []string{} @@ -139,8 +147,39 @@ func main() { armw.TestServer.SetEnabled(true) _ = armw.RecordingButton.SetText("Start recording") } + } + + armw.RecordingButton.Clicked().Attach(recordingButtonHandler) + armw.MainWindow.Hotkey().Attach(func(hkid int) { + if hkid == 1 { + recordingButtonHandler() + } }) + walk.RegisterGlobalHotKey(armw.MainWindow, 1, currentSettings.RecorderShortcut) + + shortcutRecorderHandler := func() { + if !armw.RecorderShortcutEdit.Enabled() { // start recording + armw.RecorderShortcutEdit.SetEnabled(true) + armw.RecorderShortcutEdit.SetFocus() + armw.RecorderShortcutRecordButton.SetText("Save") + if !win.UnregisterHotKey(armw.MainWindow.Handle(), 1) { + walk.MsgBox(armw.MainWindow, "Failed unregistering hotkey", "failed unregistering key, please restart application", walk.MsgBoxIconWarning) + return + } + } else { // persist new shortcut and rebind + armw.RecorderShortcutEdit.SetEnabled(false) + armw.RecorderShortcutRecordButton.SetText("Record shortcut") + if !walk.RegisterGlobalHotKey(armw.MainWindow, 1, currentSettings.RecorderShortcut) { + walk.MsgBox(armw.MainWindow, "Failed registering new hotkey", "failed registering new shortcut key, please restart application", walk.MsgBoxIconWarning) + return + } + writeConfig(currentSettings) + } + } + + armw.RecorderShortcutRecordButton.Clicked().Attach(shortcutRecorderHandler) + go func(cw *walk.CustomWidget) { t := time.NewTicker(time.Second) @@ -307,7 +346,20 @@ func readConfig() (*captureConfig, error) { } eveGameLogsFolder := filepath.Join(usr.HomeDir, "Documents", "EVE", "logs", "Gamelogs") - c = &captureConfig{AppRoot: appDir, X: 10, Y: 10, H: 400, Recordings: filepath.Join(appDir, "recordings"), FilterThreshold: 110, FilteredPreview: false, EVEGameLogsFolder: eveGameLogsFolder} + defaultShortcut := walk.Shortcut{Modifiers: walk.ModControl | walk.ModAlt, Key: walk.KeyEnd} + + c = &captureConfig{ + AppRoot: appDir, + X: 10, + Y: 10, + H: 400, + Recordings: filepath.Join(appDir, "recordings"), + FilterThreshold: 110, + FilteredPreview: false, + EVEGameLogsFolder: eveGameLogsFolder, + RecorderShortcutText: defaultShortcut.String(), + RecorderShortcut: defaultShortcut, + } load = false } else if err != nil { return c, err diff --git a/go.mod b/go.mod index 8798ff5..f82219c 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,12 @@ require ( github.com/golang/protobuf v1.4.1 github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 github.com/lxn/win v0.0.0-20210218163916-a377121e959e - golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 + golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 google.golang.org/protobuf v1.25.0 gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect ) + +replace ( + github.com/lxn/walk => github.com/shivas/walk v0.0.0-20210212094857-c0397ac21ff8 + github.com/lxn/win => github.com/shivas/win v0.0.0-20210625114026-3e83f2d215c6 +) diff --git a/go.sum b/go.sum index f17d8ca..9ffb447 100644 --- a/go.sum +++ b/go.sum @@ -25,11 +25,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw= -github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/shivas/walk v0.0.0-20210212094857-c0397ac21ff8 h1:XfZohVxBfVEqR8waXYFiLGyWwX/Z5W6BsxQx+T9lqtw= +github.com/shivas/walk v0.0.0-20210212094857-c0397ac21ff8/go.mod h1:mApxRmv/+YmW2b+EFVr2Ve7hhrxDicu73W+uP7Za+VE= +github.com/shivas/win v0.0.0-20210625114026-3e83f2d215c6 h1:8rO0BcQ2beWFsbGqFOsJVF4ABpQlkPGFB/cw9RXILws= +github.com/shivas/win v0.0.0-20210625114026-3e83f2d215c6/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= @@ -48,8 +48,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY= -golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/internal/mainwindow/mainwindow.go b/internal/mainwindow/mainwindow.go index d3b9930..982f86d 100644 --- a/internal/mainwindow/mainwindow.go +++ b/internal/mainwindow/mainwindow.go @@ -9,24 +9,30 @@ import ( . "github.com/lxn/walk/declarative" ) +type ShortcutSetter interface { + SetRecorderShortcut(walk.Shortcut) +} + type WindowComboBoxItem struct { WindowTitle string WindowHandle syscall.Handle } type AbyssRecorderWindow struct { - MainWindow *walk.MainWindow - FilteredPreview *walk.CheckBox - DataBinder *walk.DataBinder - CaptureWidget *walk.CustomWidget - HSetting *walk.NumberEdit - RecordingButton *walk.PushButton - CaptureWindowComboBox *walk.ComboBox - CombatLogCharacterGroup *walk.GroupBox - CaptureSettingsGroup *walk.GroupBox - EVEGameLogsFolderLabel *walk.TextLabel - ChooseLogDirButton *walk.PushButton - TestServer *walk.CheckBox + MainWindow *walk.MainWindow + FilteredPreview *walk.CheckBox + DataBinder *walk.DataBinder + CaptureWidget *walk.CustomWidget + HSetting *walk.NumberEdit + RecordingButton *walk.PushButton + CaptureWindowComboBox *walk.ComboBox + CombatLogCharacterGroup *walk.GroupBox + CaptureSettingsGroup *walk.GroupBox + EVEGameLogsFolderLabel *walk.TextLabel + ChooseLogDirButton *walk.PushButton + TestServer *walk.CheckBox + RecorderShortcutEdit *walk.LineEdit + RecorderShortcutRecordButton *walk.PushButton } func NewAbyssRecorderWindow(config interface{}, customWidgetPaintFunc walk.PaintFunc, comboBoxModel []*WindowComboBoxItem) *AbyssRecorderWindow { @@ -140,6 +146,34 @@ func NewAbyssRecorderWindow(config interface{}, customWidgetPaintFunc walk.Paint AlwaysConsumeSpace: true, MinSize: Size{Height: 20}, }, + Composite{ + Layout: HBox{}, + Alignment: AlignHNearVNear, + Children: []Widget{ + TextLabel{ + Text: "Start/Stop shortcut", + }, + LineEdit{ + Text: Bind("RecorderShortcutText"), + AssignTo: &obj.RecorderShortcutEdit, + OnKeyPress: func(key walk.Key) { + shortcut := walk.Shortcut{Modifiers: walk.ModifiersDown(), Key: key} + obj.RecorderShortcutEdit.SetText(shortcut.String()) + c, ok := config.(ShortcutSetter) + if ok { + c.SetRecorderShortcut(shortcut) + } + }, + Enabled: false, + ReadOnly: true, + }, + PushButton{ + AssignTo: &obj.RecorderShortcutRecordButton, + MinSize: Size{Height: 20}, + Text: "Record shortcut", + }, + }, + }, PushButton{ AssignTo: &obj.RecordingButton, MinSize: Size{Height: 40}, From 1140320a1eacebf2b5931c9762a8982a78b5b3e5 Mon Sep 17 00:00:00 2001 From: Audrius Karabanovas Date: Sat, 26 Jun 2021 01:23:48 +0300 Subject: [PATCH 2/2] Linting project --- .golangci.yaml | 61 ++++++++++++++++++++++- cmd/abyss-blackbox/main.go | 51 ++++++++++++-------- cmd/abyss-blackbox/recorder.go | 38 +++++++++------ cmd/extract/main.go | 23 ++++++--- combatlog/language.go | 6 +-- combatlog/reader.go | 22 +++++---- encoding/encoding.go | 2 + encoding/encoding_test.go | 1 - internal/mainwindow/characters.go | 1 + internal/mainwindow/mainwindow.go | 5 +- screen/screen.go | 80 ++++++++++++++++++------------- 11 files changed, 199 insertions(+), 91 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index dff8248..1721628 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,5 +1,62 @@ -# all available settings of specific linters linters-settings: + errcheck: + check-type-assertions: true + goconst: + min-len: 2 + min-occurrences: 3 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style govet: + check-shadowing: true disable: - - unsafeptr \ No newline at end of file + - unsafeptr + + nolintlint: + require-explanation: true + require-specific: true + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + - exportloopref + - exhaustive + - goconst + - gocritic + - gofmt + - goimports + - gocyclo + - gosec + - gosimple + - govet + - ineffassign + - misspell + - nolintlint + - nakedret + - prealloc + - predeclared + - revive + - staticcheck + - structcheck + - stylecheck + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - varcheck + - whitespace + - wsl + +run: + issues-exit-code: 1 \ No newline at end of file diff --git a/cmd/abyss-blackbox/main.go b/cmd/abyss-blackbox/main.go index 61a3c4e..ee1d6aa 100644 --- a/cmd/abyss-blackbox/main.go +++ b/cmd/abyss-blackbox/main.go @@ -49,6 +49,8 @@ func (c *captureConfig) SetRecorderShortcut(s walk.Shortcut) { } func main() { + var err error + currentSettings, err := readConfig() if err != nil { log.Fatal(err) @@ -62,6 +64,7 @@ func main() { clr := combatlog.NewReader(currentSettings.EVEGameLogsFolder) recorder = NewRecorder(recordingChannel, currentSettings, notificationChannel, clr) recorder.StartLoop() + defer recorder.StopLoop() foundEVEClientWindows, err := screen.FindWindow(regexp.MustCompile(EVEClientWindowRe)) @@ -71,7 +74,7 @@ func main() { comboModel = append(comboModel, &mainwindow.WindowComboBoxItem{WindowTitle: title, WindowHandle: handle}) } - logFiles, _ := clr.GetLogFiles(time.Now(), time.Duration(24*time.Hour)) + logFiles, _ := clr.GetLogFiles(time.Now(), time.Duration(24)*time.Hour) charMap := clr.MapCharactersToFiles(logFiles) armw := mainwindow.NewAbyssRecorderWindow(currentSettings, drawStuff, comboModel) @@ -87,7 +90,7 @@ func main() { if accepted { _ = armw.EVEGameLogsFolderLabel.SetText(fd.FilePath) clr.SetLogFolder(fd.FilePath) - logFiles, err := clr.GetLogFiles(time.Now(), time.Duration(24*time.Hour)) + logFiles, err = clr.GetLogFiles(time.Now(), time.Duration(24)*time.Hour) if err != nil { return } @@ -110,15 +113,16 @@ func main() { }(notificationChannel, notificationIcon) recordingButtonHandler := func() { - if recorder.Status() == RECORDER_STOPPED { - + if recorder.Status() == RecorderStopped { charsChecked := []string{} + checkboxes := armw.CombatLogCharacterGroup.Children() for i := 0; i < checkboxes.Len(); i++ { cb, ok := checkboxes.At(i).(*walk.CheckBox) if !ok { continue } + if cb.Checked() { charsChecked = append(charsChecked, cb.Text()) } @@ -135,9 +139,8 @@ func main() { armw.ChooseLogDirButton.SetEnabled(false) armw.TestServer.SetEnabled(false) _ = armw.RecordingButton.SetText("Stop recording") - } else { - err := recorder.Stop() + err = recorder.Stop() if err != nil { walk.MsgBox(armw.MainWindow, "Error writing recording", err.Error(), walk.MsgBoxIconWarning) } @@ -161,36 +164,35 @@ func main() { shortcutRecorderHandler := func() { if !armw.RecorderShortcutEdit.Enabled() { // start recording armw.RecorderShortcutEdit.SetEnabled(true) - armw.RecorderShortcutEdit.SetFocus() - armw.RecorderShortcutRecordButton.SetText("Save") + _ = armw.RecorderShortcutEdit.SetFocus() + _ = armw.RecorderShortcutRecordButton.SetText("Save") + if !win.UnregisterHotKey(armw.MainWindow.Handle(), 1) { walk.MsgBox(armw.MainWindow, "Failed unregistering hotkey", "failed unregistering key, please restart application", walk.MsgBoxIconWarning) return } } else { // persist new shortcut and rebind armw.RecorderShortcutEdit.SetEnabled(false) - armw.RecorderShortcutRecordButton.SetText("Record shortcut") + _ = armw.RecorderShortcutRecordButton.SetText("Record shortcut") if !walk.RegisterGlobalHotKey(armw.MainWindow, 1, currentSettings.RecorderShortcut) { walk.MsgBox(armw.MainWindow, "Failed registering new hotkey", "failed registering new shortcut key, please restart application", walk.MsgBoxIconWarning) return } - writeConfig(currentSettings) + _ = writeConfig(currentSettings) } } armw.RecorderShortcutRecordButton.Clicked().Attach(shortcutRecorderHandler) go func(cw *walk.CustomWidget) { - t := time.NewTicker(time.Second) for range t.C { - - img, err := screen.CaptureWindowArea( + img, errr := screen.CaptureWindowArea( foundEVEClientWindows.GetHandleByTitle(currentSettings.EVEClientWindowTitle), image.Rectangle{Min: image.Point{X: currentSettings.X, Y: currentSettings.Y}, Max: image.Point{X: currentSettings.X + 255, Y: currentSettings.Y + currentSettings.H}}, ) - if err != nil { + if errr != nil { walk.MsgBox(armw.MainWindow, "Error capturing window area, restart of application is needed.", err.Error(), walk.MsgBoxIconWarning) fmt.Printf("error lost capture window: %v", err) os.Exit(1) // exit @@ -212,7 +214,6 @@ func main() { default: log.Println("preview channel full, dropping frame") } - } select { @@ -223,7 +224,6 @@ func main() { win.InvalidateRect(cw.Handle(), nil, false) } - }(armw.CaptureWidget) walk.Clipboard().ContentsChanged().Attach(recorder.ClipboardListener) @@ -261,6 +261,7 @@ func CreateNotificationIcon(mw *walk.MainWindow) *walk.NotifyIcon { if err := ni.SetIcon(icon); err != nil { log.Fatal(err) } + if err := ni.SetToolTip("Click for info or use the context menu to exit."); err != nil { log.Fatal(err) } @@ -272,6 +273,7 @@ func CreateNotificationIcon(mw *walk.MainWindow) *walk.NotifyIcon { } exitAction.Triggered().Attach(func() { walk.App().Exit(0) }) + if err := ni.ContextMenu().Actions().Add(exitAction); err != nil { log.Fatal(err) } @@ -291,6 +293,7 @@ func drawStuff(canvas *walk.Canvas, updateBounds walk.Rectangle) error { if err != nil { return err } + defer bmp.Dispose() err = canvas.DrawImagePixels(bmp, walk.Point{X: 0, Y: 0}) @@ -300,6 +303,7 @@ func drawStuff(canvas *walk.Canvas, updateBounds walk.Rectangle) error { default: return nil } + return nil } @@ -308,22 +312,25 @@ func paletted(img *image.NRGBA, cutoff uint32) *image.Paletted { palette := []color.Color{color.White, color.Black} buffimg := image.NewPaletted(img.Rect, palette) - var threshold uint32 = cutoff << 8 + var threshold = cutoff << 8 for y := 0; y < img.Rect.Dy(); y++ { for x := 0; x < img.Rect.Dx(); x++ { c := img.At(x, y) r, _, _, _ := c.RGBA() + if r > threshold { buffimg.SetColorIndex(x, y, 0) continue } + if r <= threshold { buffimg.SetColorIndex(x, y, 1) continue } } } + return buffimg } @@ -335,15 +342,18 @@ func readConfig() (*captureConfig, error) { } var c *captureConfig + load := true settingsFilename := filepath.Join(appDir, "settings.json") + _, err = os.Stat(settingsFilename) if os.IsNotExist(err) { // fetch current user folder and try to point to Gamelogs folder - usr, err := user.Current() - if err != nil { - return nil, err + usr, errr := user.Current() + if errr != nil { + return nil, errr } + eveGameLogsFolder := filepath.Join(usr.HomeDir, "Documents", "EVE", "logs", "Gamelogs") defaultShortcut := walk.Shortcut{Modifiers: walk.ModControl | walk.ModAlt, Key: walk.KeyEnd} @@ -384,6 +394,7 @@ func readConfig() (*captureConfig, error) { // writeConfig saves configuration to json file func writeConfig(c *captureConfig) error { settingsFilename := filepath.Join(c.AppRoot, "settings.json") + f, err := os.Create(settingsFilename) if err != nil { return err diff --git a/cmd/abyss-blackbox/recorder.go b/cmd/abyss-blackbox/recorder.go index bef6680..eea1b83 100644 --- a/cmd/abyss-blackbox/recorder.go +++ b/cmd/abyss-blackbox/recorder.go @@ -18,9 +18,9 @@ import ( ) const ( - RECORDER_STOPPED = iota - RECORDER_RUNNING - RECORDER_AWAITING_INITIAL_LOOT + RecorderStopped = iota + RecorderRunning + RecorderAwaitingInitialLoot ) type Recorder struct { @@ -44,7 +44,7 @@ func NewRecorder(frameChan chan *image.Paletted, c *captureConfig, nc chan Notif return &Recorder{ frameChan: frameChan, loot: make(chan string, 2), - state: RECORDER_STOPPED, + state: RecorderStopped, config: c, frames: make([]*image.Paletted, 0), delays: make([]int, 0), @@ -75,7 +75,6 @@ func (r *Recorder) ClipboardListener() { // StartLoop starts main recorded loop listening for frames and clipboard changes func (r *Recorder) StartLoop() { go func(r *Recorder) { - for { select { case <-r.done: @@ -85,13 +84,15 @@ func (r *Recorder) StartLoop() { r.Lock() switch r.state { - case RECORDER_AWAITING_INITIAL_LOOT: + case RecorderAwaitingInitialLoot: r.notificationChannel <- NotificationMessage{Title: "Abyssal.Space recording started...", Message: "Initial cargo received, awaiting cargo after fillament activation"} - r.state = RECORDER_RUNNING + r.state = RecorderRunning r.lootRecords = append(r.lootRecords, &encoding.LootRecord{Frame: 0, Loot: lootSnapshot}) - case RECORDER_RUNNING: + case RecorderRunning: r.notificationChannel <- NotificationMessage{Title: "Abyssal.Space recorder", Message: "Loot captured from clipboard!"} + lr := &encoding.LootRecord{Frame: int32(len(r.frames) - 1), Loot: lootSnapshot} + log.Printf("loot appended: %v\n", lr) r.lootRecords = append(r.lootRecords, lr) default: @@ -102,7 +103,7 @@ func (r *Recorder) StartLoop() { case frame := <-r.frameChan: r.Lock() - if r.state == RECORDER_RUNNING { // append to buffer + if r.state == RecorderRunning { // append to buffer r.frames = append(r.frames, frame) r.delays = append(r.delays, 10) } @@ -124,7 +125,7 @@ func (r *Recorder) Start(characters []string) { if os.IsNotExist(err) { err = os.MkdirAll(r.config.Recordings, os.ModeDir) if err != nil { - log.Fatalf("could not create folder: %s", r.config.Recordings) + log.Fatalf("could not create folder: %s", r.config.Recordings) //nolint:gocritic // it's dead jim } } @@ -134,24 +135,28 @@ func (r *Recorder) Start(characters []string) { // remove log files from tracking for char := range r.charactersTracking { found := false + for _, selected := range characters { if char == selected { found = true break } } + if !found { delete(r.charactersTracking, char) } } + log.Printf("recording characters: %+v\n", r.charactersTracking) + r.combatlogReader.MarkStartOffsets(r.charactersTracking) r.recordingName = filepath.Join(r.config.Recordings, fmt.Sprintf("%s.abyss", time.Now().Format("2006-Jan-2-15-04-05"))) r.frames = make([]*image.Paletted, 0) r.delays = make([]int, 0) r.lootRecords = make([]*encoding.LootRecord, 0) - r.state = RECORDER_AWAITING_INITIAL_LOOT + r.state = RecorderAwaitingInitialLoot r.notificationChannel <- NotificationMessage{Title: "Recording starting...", Message: "CTRL+A, CTRL+C your inventory"} } @@ -161,8 +166,8 @@ func (r *Recorder) Stop() error { defer r.Unlock() if len(r.frames) == 0 { - r.state = RECORDER_STOPPED - return fmt.Errorf("There was no frames captured, skipping recording of abyss run") + r.state = RecorderStopped + return fmt.Errorf("there was no frames captured, skipping recording of abyss run") } file, _ := os.Create(r.recordingName) @@ -172,15 +177,16 @@ func (r *Recorder) Stop() error { var buf bytes.Buffer - r.state = RECORDER_STOPPED + r.state = RecorderStopped anim := gif.GIF{Delay: r.delays, LoopCount: -1, Image: r.frames} + err := gif.EncodeAll(&buf, &anim) if err != nil { return err } defer func() { - r.notificationChannel <- NotificationMessage{Title: "Abyss recorder", Message: fmt.Sprintf("Abyss run succesfully recorded to file: %s", r.recordingName)} + r.notificationChannel <- NotificationMessage{Title: "Abyss recorder", Message: fmt.Sprintf("Abyss run successfully recorded to file: %s", r.recordingName)} }() defer func() { @@ -195,6 +201,7 @@ func (r *Recorder) Stop() error { CombatLog: r.combatlogReader.GetCombatLogRecords(r.charactersTracking), TestServer: r.config.TestServer, } + return abyssFile.Encode(file) } @@ -207,5 +214,6 @@ func (r *Recorder) StopLoop() { func (r *Recorder) Status() int { r.Lock() defer r.Unlock() + return r.state } diff --git a/cmd/extract/main.go b/cmd/extract/main.go index da1a77c..069b081 100644 --- a/cmd/extract/main.go +++ b/cmd/extract/main.go @@ -13,6 +13,8 @@ import ( ) func main() { + var err error + if len(os.Args) < 2 { fmt.Println("usage: extract recording.abyss") os.Exit(1) @@ -26,20 +28,23 @@ func main() { abyssFile, err := encoding.Decode(file) if err != nil { - log.Fatal(err) + log.Print(err) + return } gifName := filepath.Base(os.Args[1]) + ".gif" gifFile, err := os.Create(gifName) if err != nil { - log.Fatal(err) + log.Print(err) + return } defer gifFile.Close() _, err = io.Copy(gifFile, bytes.NewReader(abyssFile.Overview)) if err != nil { - log.Fatal(err) + log.Print(err) + return } if abyssFile.TestServer { @@ -50,9 +55,10 @@ func main() { for _, logRecord := range abyssFile.CombatLog { fmt.Printf("combat log record language for character %q: %s\n", logRecord.CharacterName, logRecord.GetLanguageCode().String()) - f, err := os.Create(logRecord.CharacterName + ".combatlog.txt") - if err != nil { - log.Println(err) + + f, errr := os.Create(logRecord.CharacterName + ".combatlog.txt") + if errr != nil { + log.Println(errr) } for _, l := range logRecord.CombatLogLines { @@ -61,6 +67,7 @@ func main() { log.Println(err) } } + f.Close() } @@ -70,8 +77,10 @@ func main() { } fmt.Fprintf(f, "Loot recordings:") + for _, lootRecord := range abyssFile.Loot { - fmt.Fprintf(f, "time: %s\n%s\n\n", time.Duration(time.Duration(lootRecord.Frame)*time.Second).String(), lootRecord.Loot) + fmt.Fprintf(f, "time: %s\n%s\n\n", time.Duration(lootRecord.Frame)*time.Second, lootRecord.Loot) } + f.Close() } diff --git a/combatlog/language.go b/combatlog/language.go index 0b93a92..f22d507 100644 --- a/combatlog/language.go +++ b/combatlog/language.go @@ -4,7 +4,7 @@ import "regexp" //go:generate protoc -I ../protobuf/ --go_opt=module=github.com/shivas/abyss-blackbox --go_out=.. combatlog.proto -// LanguageMatchers holds default initialized regexps for all languages +// LanguageMatchers holds default initialized regexps for all languages. var LanguageMatchers LocalizedMatchers func init() { @@ -46,11 +46,11 @@ func init() { } } -// LocalizedMatcher holds regexps for language to detect +// LocalizedMatcher holds regexps for language to detect. type LocalizedMatcher struct { ListenerRe *regexp.Regexp SessionStartRe *regexp.Regexp } -// LocalizedMatchers mapping between LanguageCode and LocalizedMatchers +// LocalizedMatchers mapping between LanguageCode and LocalizedMatchers. type LocalizedMatchers map[LanguageCode]LocalizedMatcher diff --git a/combatlog/reader.go b/combatlog/reader.go index ff422b3..4732eb4 100644 --- a/combatlog/reader.go +++ b/combatlog/reader.go @@ -27,9 +27,8 @@ func (r *Reader) SetLogFolder(folder string) { r.logDir = folder } -// GetLogFiles returns slice of filepaths that logged in in last timeWindow +// GetLogFiles returns slice of filepaths that logged in in last timeWindow. func (r *Reader) GetLogFiles(end time.Time, timeWindow time.Duration) ([]string, error) { - prefixes := genPrefixes(end, timeWindow) logFiles := []string{} @@ -39,17 +38,18 @@ func (r *Reader) GetLogFiles(end time.Time, timeWindow time.Duration) ([]string, if err != nil { return nil, err } + logFiles = append(logFiles, matches...) } return logFiles, nil } -// GetCombatLogRecords reads combatlog from stored offsets and converts to CombatLogRecord struct +// GetCombatLogRecords reads combatlog from stored offsets and converts to CombatLogRecord struct. func (r *Reader) GetCombatLogRecords(characters map[string]CombatLogFile) []*CombatLogRecord { recordings := make([]*CombatLogRecord, 0) - for character, logfile := range characters { + for character, logfile := range characters { startFileInfo, marked := r.startOffsets[character] if !marked { continue @@ -72,6 +72,7 @@ func (r *Reader) GetCombatLogRecords(characters map[string]CombatLogFile) []*Com for scanner.Scan() { clr.CombatLogLines = append(clr.CombatLogLines, scanner.Text()) } + recordings = append(recordings, clr) } @@ -80,10 +81,9 @@ func (r *Reader) GetCombatLogRecords(characters map[string]CombatLogFile) []*Com return recordings } -// MarkStartOffsets stores offsets of combatlog files +// MarkStartOffsets stores offsets of combatlog files. func (r *Reader) MarkStartOffsets(characters map[string]CombatLogFile) { for character, logfile := range characters { - file, err := os.Open(logfile.Filename) if err != nil { continue @@ -94,13 +94,13 @@ func (r *Reader) MarkStartOffsets(characters map[string]CombatLogFile) { if err != nil { continue } + r.startOffsets[character] = fi } } -// MapCharactersToFiles maps given paths to characters, detecting combatlog language in process +// MapCharactersToFiles maps given paths to characters, detecting combatlog language in process. func (r *Reader) MapCharactersToFiles(files []string) map[string]CombatLogFile { - type f struct { sessionStarted *time.Time filename string @@ -118,6 +118,7 @@ func (r *Reader) MapCharactersToFiles(files []string) map[string]CombatLogFile { defer file.Close() reader := bufio.NewScanner(file) + var insideBanner bool logFile := f{filename: filename} @@ -128,6 +129,7 @@ func (r *Reader) MapCharactersToFiles(files []string) map[string]CombatLogFile { insideBanner = true continue } + if insideBanner { break // reading closing header separator, just break } @@ -136,6 +138,7 @@ func (r *Reader) MapCharactersToFiles(files []string) map[string]CombatLogFile { if insideBanner { listenerText := reader.Text() sessionText := reader.Text() + for languageCode, matchers := range LanguageMatchers { if matches := matchers.ListenerRe.FindAllStringSubmatch(listenerText, 1); matches != nil { logFile.character = &matches[0][1] @@ -147,6 +150,7 @@ func (r *Reader) MapCharactersToFiles(files []string) map[string]CombatLogFile { if err != nil { continue } + logFile.sessionStarted = &sessionStart } } @@ -162,10 +166,10 @@ func (r *Reader) MapCharactersToFiles(files []string) map[string]CombatLogFile { } else { tempMap[*logFile.character] = logFile } + break } } - } result := make(map[string]CombatLogFile, len(tempMap)) diff --git a/encoding/encoding.go b/encoding/encoding.go index 48c29bf..6853ade 100644 --- a/encoding/encoding.go +++ b/encoding/encoding.go @@ -22,6 +22,7 @@ func (rf *AbyssRecording) Encode(w io.Writer) error { defer gw.Close() _, err = io.Copy(gw, bytes.NewReader(data)) + return err } @@ -41,5 +42,6 @@ func Decode(r io.Reader) (*AbyssRecording, error) { } err = proto.Unmarshal(data, result) + return result, err } diff --git a/encoding/encoding_test.go b/encoding/encoding_test.go index 1ee7d7d..2b536ef 100644 --- a/encoding/encoding_test.go +++ b/encoding/encoding_test.go @@ -9,7 +9,6 @@ import ( ) func TestAbyssRecording_Encode(t *testing.T) { - record := AbyssRecording{ Overview: []byte{1, 2, 3}, Loot: []*LootRecord{ diff --git a/internal/mainwindow/characters.go b/internal/mainwindow/characters.go index c5e08ea..d44a69a 100644 --- a/internal/mainwindow/characters.go +++ b/internal/mainwindow/characters.go @@ -9,6 +9,7 @@ func BuildSettingsWidget(characters map[string]combatlog.CombatLogFile, parent w for i := 0; i < parent.Children().Len(); i++ { parent.Children().At(i).Dispose() } + for charName := range characters { cb, _ := walk.NewCheckBox(parent) _ = cb.SetText(charName) diff --git a/internal/mainwindow/mainwindow.go b/internal/mainwindow/mainwindow.go index 982f86d..d150263 100644 --- a/internal/mainwindow/mainwindow.go +++ b/internal/mainwindow/mainwindow.go @@ -6,7 +6,7 @@ import ( "time" "github.com/lxn/walk" - . "github.com/lxn/walk/declarative" + . "github.com/lxn/walk/declarative" // nolint:stylecheck,revive // we needs side effects ) type ShortcutSetter interface { @@ -35,6 +35,7 @@ type AbyssRecorderWindow struct { RecorderShortcutRecordButton *walk.PushButton } +// NewAbyssRecorderWindow creates new main window of recorder. func NewAbyssRecorderWindow(config interface{}, customWidgetPaintFunc walk.PaintFunc, comboBoxModel []*WindowComboBoxItem) *AbyssRecorderWindow { obj := AbyssRecorderWindow{} @@ -158,7 +159,7 @@ func NewAbyssRecorderWindow(config interface{}, customWidgetPaintFunc walk.Paint AssignTo: &obj.RecorderShortcutEdit, OnKeyPress: func(key walk.Key) { shortcut := walk.Shortcut{Modifiers: walk.ModifiersDown(), Key: key} - obj.RecorderShortcutEdit.SetText(shortcut.String()) + _ = obj.RecorderShortcutEdit.SetText(shortcut.String()) c, ok := config.(ShortcutSetter) if ok { c.SetRecorderShortcut(shortcut) diff --git a/screen/screen.go b/screen/screen.go index e1ab1f0..58ae78a 100644 --- a/screen/screen.go +++ b/screen/screen.go @@ -19,7 +19,7 @@ func init() { // tell us the scale factor for our monitor so that our screenshot works // on hi-res displays. // PROCESS_PER_MONITOR_DPI_AWARE - procSetProcessDpiAwareness.Call(uintptr(2)) // nolint + procSetProcessDpiAwareness.Call(uintptr(2)) //nolint:errcheck // no needed } func CaptureWindowArea(handle syscall.Handle, rect image.Rectangle) (image.Image, error) { @@ -36,12 +36,14 @@ var ( modGdi32 = syscall.NewLazyDLL("Gdi32.dll") procBitBlt = modGdi32.NewProc("BitBlt") - //procCreateCompatibleBitmap = modGdi32.NewProc("CreateCompatibleBitmap") + + // procCreateCompatibleBitmap = modGdi32.NewProc("CreateCompatibleBitmap") procCreateCompatibleDC = modGdi32.NewProc("CreateCompatibleDC") procCreateDIBSection = modGdi32.NewProc("CreateDIBSection") procDeleteDC = modGdi32.NewProc("DeleteDC") procDeleteObject = modGdi32.NewProc("DeleteObject") - //procGetDeviceCaps = modGdi32.NewProc("GetDeviceCaps") + + // procGetDeviceCaps = modGdi32.NewProc("GetDeviceCaps") procSelectObject = modGdi32.NewProc("SelectObject") modShcore = syscall.NewLazyDLL("Shcore.dll") @@ -50,22 +52,22 @@ var ( const ( // BitBlt constants - bitBlt_SRCCOPY = 0x00CC0020 + bitBltSRCCOPY = 0x00CC0020 ) // Windows RECT structure -type win_RECT struct { +type winRect struct { Left, Top, Right, Bottom int32 } // http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx -type win_BITMAPINFO struct { - BmiHeader win_BITMAPINFOHEADER - BmiColors *win_RGBQUAD +type winBITMAPINFO struct { + BmiHeader winBITMAPINFOHEADER + BmiColors *winRGBQUAD } // http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx -type win_BITMAPINFOHEADER struct { +type winBITMAPINFOHEADER struct { BiSize uint32 BiWidth int32 BiHeight int32 @@ -80,7 +82,7 @@ type win_BITMAPINFOHEADER struct { } // http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx -type win_RGBQUAD struct { +type winRGBQUAD struct { RgbBlue byte RgbGreen byte RgbRed byte @@ -96,6 +98,7 @@ func (fw FoundWindows) GetHandleByTitle(title string) syscall.Handle { return handle } } + return 0 } @@ -104,6 +107,7 @@ func (fw FoundWindows) GetWindowsTitles() []string { for _, wtitle := range fw { result = append(result, wtitle) } + return result } @@ -115,46 +119,51 @@ func FindWindow(title *regexp.Regexp) (FoundWindows, error) { _, err := GetWindowText(h, &b[0], int32(len(b))) if err != nil { // ignore the error - return 1 // continue enumeration + return 1 // enumeration continue } windowTitle := windows.UTF16ToString(b) if title.Find([]byte(windowTitle)) != nil { // note the window results[h] = windowTitle - // return 0 // stop enumeration } - return 1 // continue enumeration + return 1 // enumeration continue }) + err := enumWindows(cb, 0) if err != nil { return results, err } + if len(results) == 0 { - return results, fmt.Errorf("No window with title '%s' found", title.String()) + return results, fmt.Errorf("no window with title '%s' found", title.String()) } + return results, nil } // GetWindowText returns window title -func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) { +func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (length int32, err error) { r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount)) - len = int32(r0) - if len == 0 { + + length = int32(r0) + if length == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } + return } // WindowRect gets the dimensions for a Window handle. func WindowRect(hwnd syscall.Handle) (image.Rectangle, error) { - var rect win_RECT + var rect winRect + ret, _, err := procGetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&rect))) if ret == 0 { - return image.Rectangle{}, fmt.Errorf("Error getting window dimensions: %s", err) + return image.Rectangle{}, fmt.Errorf("error getting window dimensions: %s", err) } return image.Rect(0, 0, int(rect.Right), int(rect.Bottom)), nil @@ -165,24 +174,26 @@ func captureWindow(handle syscall.Handle, rect image.Rectangle) (image.Image, er // Get the device context for screenshotting dcSrc, _, err := procGetDC.Call(uintptr(handle)) if dcSrc == 0 { - return nil, fmt.Errorf("Error preparing screen capture: %s", err) + return nil, fmt.Errorf("error preparing screen capture: %s", err) } - defer procReleaseDC.Call(0, dcSrc) // nolint + + defer procReleaseDC.Call(0, dcSrc) // nolint:errcheck // it's fine // Grab a compatible DC for drawing dcDst, _, err := procCreateCompatibleDC.Call(dcSrc) if dcDst == 0 { - return nil, fmt.Errorf("Error creating DC for drawing: %s", err) + return nil, fmt.Errorf("error creating DC for drawing: %s", err) } - defer procDeleteDC.Call(dcDst) // nolint + + defer procDeleteDC.Call(dcDst) // nolint:errcheck // it's fine // Determine the width/height of our capture width := rect.Dx() height := rect.Dy() // Get the bitmap we're going to draw onto - var bitmapInfo win_BITMAPINFO - bitmapInfo.BmiHeader = win_BITMAPINFOHEADER{ + var bitmapInfo winBITMAPINFO + bitmapInfo.BmiHeader = winBITMAPINFOHEADER{ BiSize: uint32(reflect.TypeOf(bitmapInfo.BmiHeader).Size()), BiWidth: int32(width), BiHeight: int32(height), @@ -190,24 +201,28 @@ func captureWindow(handle syscall.Handle, rect image.Rectangle) (image.Image, er BiBitCount: 32, BiCompression: 0, // BI_RGB } + bitmapData := unsafe.Pointer(uintptr(0)) bitmap, _, err := procCreateDIBSection.Call( dcDst, uintptr(unsafe.Pointer(&bitmapInfo)), 0, uintptr(unsafe.Pointer(&bitmapData)), 0, 0) + if bitmap == 0 { - return nil, fmt.Errorf("Error creating bitmap for screen capture: %s", err) + return nil, fmt.Errorf("error creating bitmap for screen capture: %s", err) } - defer procDeleteObject.Call(bitmap) // nolint + + defer procDeleteObject.Call(bitmap) //nolint:errcheck // no needed // Select the object and paint it - procSelectObject.Call(dcDst, bitmap) // nolint + procSelectObject.Call(dcDst, bitmap) //nolint:errcheck // no needed ret, _, err := procBitBlt.Call( dcDst, 0, 0, uintptr(width), uintptr(height), - dcSrc, uintptr(rect.Min.X), uintptr(rect.Min.Y), bitBlt_SRCCOPY) + dcSrc, uintptr(rect.Min.X), uintptr(rect.Min.Y), bitBltSRCCOPY) + if ret == 0 { - return nil, fmt.Errorf("Error capturing screen: %s", err) + return nil, fmt.Errorf("error capturing screen: %s", err) } // Convert the bitmap to an image.Image. We first start by directly @@ -232,8 +247,8 @@ func captureWindow(handle syscall.Handle, rect image.Rectangle) (image.Image, er return dst, nil } -func enumWindows(enumFunc uintptr, lparam uintptr) (err error) { - r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0) +func enumWindows(enumFunc, lparam uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, enumFunc, lparam, 0) if r1 == 0 { if e1 != 0 { err = error(e1) @@ -241,5 +256,6 @@ func enumWindows(enumFunc uintptr, lparam uintptr) (err error) { err = syscall.EINVAL } } + return }