diff options
-rw-r--r-- | xA/Makefile | 6 | ||||
-rw-r--r-- | xA/xA.go | 162 | ||||
-rw-r--r-- | xM/main.swift | 3 | ||||
-rw-r--r-- | xP/public/xP.js | 2 | ||||
-rw-r--r-- | xW/xW.cpp | 181 |
5 files changed, 211 insertions, 143 deletions
diff --git a/xA/Makefile b/xA/Makefile index da62944..d0f0449 100644 --- a/xA/Makefile +++ b/xA/Makefile @@ -4,8 +4,10 @@ AWK = env LC_ALL=C awk tools = ../liberty/tools -outputs = FyneApp.toml xA proto.go xA.png xA-highlighted.png beep.raw +generated = FyneApp.toml xA.png xA-highlighted.png beep.raw proto.go +outputs = xA $(generated) all: $(outputs) +generate: $(generated) FyneApp.toml: ../xK-version printf "\ @@ -27,7 +29,7 @@ beep.raw: proto.go: $(tools)/lxdrgen.awk $(tools)/lxdrgen-go.awk ../xC.lxdr $(AWK) -f $(tools)/lxdrgen.awk -f $(tools)/lxdrgen-go.awk \ -v PrefixCamel=Relay ../xC.lxdr > $@ -xA: xA.go proto.go ../xK-version xA.png xA-highlighted.png beep.raw +xA: xA.go ../xK-version $(generated) go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@ \ -gcflags=all="-N -l" clean: @@ -281,7 +281,11 @@ func beep() { } go func() { <-otoReady - otoContext.NewPlayer(bytes.NewReader(beepSample)).Play() + p := otoContext.NewPlayer(bytes.NewReader(beepSample)) + p.Play() + for p.IsPlaying() { + time.Sleep(time.Second) + } }() } @@ -363,50 +367,6 @@ func bufferByName(name string) *buffer { return nil } -func bufferActivate(name string) { - relaySend(RelayCommandData{ - Variant: &RelayCommandDataBufferActivate{BufferName: name}, - }, nil) -} - -func bufferToggleUnimportant(name string) { - relaySend(RelayCommandData{ - Variant: &RelayCommandDataBufferToggleUnimportant{BufferName: name}, - }, nil) -} - -// --- Current buffer ---------------------------------------------------------- - -func bufferToggleLogFinish(err string, response *RelayResponseDataBufferLog) { - if response == nil { - showErrorMessage(err) - return - } - - wLog.SetText(string(response.Log)) - wLog.Show() - wRichScroll.Hide() -} - -func bufferToggleLog() { - if wLog.Visible() { - wRichScroll.Show() - wLog.Hide() - wLog.SetText("") - return - } - - name := bufferCurrent - relaySend(RelayCommandData{Variant: &RelayCommandDataBufferLog{ - BufferName: name, - }}, func(err string, response *RelayResponseData) { - if bufferCurrent == name { - bufferToggleLogFinish( - err, response.Variant.(*RelayResponseDataBufferLog)) - } - }) -} - func bufferAtBottom() bool { return wRichScroll.Offset.Y >= wRichScroll.Content.Size().Height-wRichScroll.Size().Height @@ -421,22 +381,31 @@ func bufferScrollToBottom() { refreshStatus() } +func bufferPushLine(b *buffer, line bufferLine) { + b.lines = append(b.lines, line) + + // Fyne's text layouting is extremely slow. + // The limit could be made configurable, + // and we could use a ring buffer approach to storing the lines. + if len(b.lines) > 100 { + b.lines = slices.Delete(b.lines, 0, 1) + } +} + // --- UI state refresh -------------------------------------------------------- func refreshIcon() { - highlighted := false + resource := resourceIconNormal for _, b := range buffers { if b.highlighted { - highlighted = true + resource = resourceIconHighlighted break } } - if highlighted { - wWindow.SetIcon(resourceIconHighlighted) - } else { - wWindow.SetIcon(resourceIconNormal) - } + // Prevent deadlocks (though it might have a race condition). + // https://github.com/fyne-io/fyne/issues/5266 + go func() { wWindow.SetIcon(resource) }() } func refreshTopic(topic []bufferLineItem) { @@ -504,6 +473,63 @@ func refreshStatus() { wStatus.SetText(status) } +func recheckHighlighted() { + // Corresponds to the logic toggling the bool on. + if b := bufferByName(bufferCurrent); b != nil && + b.highlighted && bufferAtBottom() && + inForeground && !wLog.Visible() { + b.highlighted = false + refreshIcon() + refreshBufferList() + } +} + +// --- Buffer actions ---------------------------------------------------------- + +func bufferActivate(name string) { + relaySend(RelayCommandData{ + Variant: &RelayCommandDataBufferActivate{BufferName: name}, + }, nil) +} + +func bufferToggleUnimportant(name string) { + relaySend(RelayCommandData{ + Variant: &RelayCommandDataBufferToggleUnimportant{BufferName: name}, + }, nil) +} + +func bufferToggleLogFinish(err string, response *RelayResponseDataBufferLog) { + if response == nil { + showErrorMessage(err) + return + } + + wLog.SetText(string(response.Log)) + wLog.Show() + wRichScroll.Hide() +} + +func bufferToggleLog() { + if wLog.Visible() { + wRichScroll.Show() + wLog.Hide() + wLog.SetText("") + + recheckHighlighted() + return + } + + name := bufferCurrent + relaySend(RelayCommandData{Variant: &RelayCommandDataBufferLog{ + BufferName: name, + }}, func(err string, response *RelayResponseData) { + if bufferCurrent == name { + bufferToggleLogFinish( + err, response.Variant.(*RelayResponseDataBufferLog)) + } + }) +} + // --- RichText formatting ----------------------------------------------------- func defaultBufferLineItem() bufferLineItem { return bufferLineItem{} } @@ -745,6 +771,7 @@ func refreshBuffer(b *buffer) { bufferPrintAndWatchTrailingDateChanges() wRichText.Refresh() bufferScrollToBottom() + recheckHighlighted() } // --- Event processing -------------------------------------------------------- @@ -755,7 +782,7 @@ func relayProcessBufferLine(b *buffer, m *RelayEventDataBufferLine) { // Initial sync: skip all other processing, let highlights be. bc := bufferByName(bufferCurrent) if bc == nil { - b.lines = append(b.lines, line) + bufferPushLine(b, line) return } @@ -767,7 +794,7 @@ func relayProcessBufferLine(b *buffer, m *RelayEventDataBufferLine) { separate := display && !visible && bc.newMessages == 0 && bc.newUnimportantMessages == 0 - b.lines = append(b.lines, line) + bufferPushLine(b, line) if !(visible || m.LeakToActive) || b.newMessages != 0 || b.newUnimportantMessages != 0 { if line.isUnimportant || m.LeakToActive { @@ -780,7 +807,7 @@ func relayProcessBufferLine(b *buffer, m *RelayEventDataBufferLine) { if m.LeakToActive { leakedLine := line leakedLine.leaked = true - bc.lines = append(bc.lines, leakedLine) + bufferPushLine(bc, leakedLine) if !visible || bc.newMessages != 0 || bc.newUnimportantMessages != 0 { if line.isUnimportant { @@ -910,11 +937,11 @@ func relayProcessMessage(m *RelayEventMessage) { b.bufferName = data.New - refreshBufferList() if data.BufferName == bufferCurrent { bufferCurrent = data.New refreshStatus() } + refreshBufferList() if data.BufferName == bufferLast { bufferLast = data.New } @@ -1311,7 +1338,8 @@ func (e *inputEntry) SetText(text string) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - type logEntry struct { - // XXX: Sadly, we can't seem to make it read-only in any way. + // XXX: Sadly, we can't seem to make it actually read-only. + // https://github.com/fyne-io/fyne/issues/5263 widget.Entry } @@ -1323,6 +1351,12 @@ func newLogEntry() *logEntry { return e } +func (e *logEntry) SetText(text string) { + e.OnChanged = nil + e.Entry.SetText(text) + e.OnChanged = func(string) { e.Entry.SetText(text) } +} + func (e *logEntry) AcceptsTab() bool { return false } @@ -1356,6 +1390,9 @@ func (l *customLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { } if toBottom { bufferScrollToBottom() + } else { + recheckHighlighted() + refreshStatus() } } @@ -1472,16 +1509,14 @@ func main() { a := app.New() a.Settings().SetTheme(&customTheme{}) + a.SetIcon(resourceIconNormal) wWindow = a.NewWindow(projectName) wWindow.Resize(fyne.NewSize(640, 480)) a.Lifecycle().SetOnEnteredForeground(func() { // TODO(p): Does this need locking? inForeground = true - if b := bufferByName(bufferCurrent); b != nil { - b.highlighted = false - refreshIcon() - } + recheckHighlighted() }) a.Lifecycle().SetOnExitedForeground(func() { inForeground = false @@ -1522,7 +1557,10 @@ func main() { wRichText = widget.NewRichText() wRichText.Wrapping = fyne.TextWrapWord wRichScroll = container.NewVScroll(wRichText) - wRichScroll.OnScrolled = func(position fyne.Position) { refreshStatus() } + wRichScroll.OnScrolled = func(position fyne.Position) { + recheckHighlighted() + refreshStatus() + } wLog = newLogEntry() wLog.Wrapping = fyne.TextWrapWord wLog.Hide() diff --git a/xM/main.swift b/xM/main.swift index 91e3499..48f26c4 100644 --- a/xM/main.swift +++ b/xM/main.swift @@ -842,11 +842,11 @@ relayRPC.onEvent = { message in b.bufferName = data.new - refreshBufferList() if b.bufferName == relayBufferCurrent { relayBufferCurrent = data.new refreshStatus() } + refreshBufferList() if b.bufferName == relayBufferLast { relayBufferLast = data.new } @@ -1203,6 +1203,7 @@ class WindowDelegate: NSObject, NSWindowDelegate { b.highlighted = false refreshIcon() + refreshBufferList() } // Buffer indexes rotated to start after the current buffer. diff --git a/xP/public/xP.js b/xP/public/xP.js index 5436a65..6035db3 100644 --- a/xP/public/xP.js +++ b/xP/public/xP.js @@ -1,4 +1,4 @@ -// Copyright (c) 2022 - 2023, Přemysl Eric Janouch <p@janouch.name> +// Copyright (c) 2022 - 2024, Přemysl Eric Janouch <p@janouch.name> // SPDX-License-Identifier: 0BSD import * as Relay from './proto.js' @@ -1,7 +1,7 @@ /* * xW.cpp: Win32 frontend for xC * - * Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name> + * Copyright (c) 2023 - 2024, Přemysl Eric Janouch <p@janouch.name> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. @@ -255,73 +255,6 @@ buffer_by_name(const std::wstring &name) return nullptr; } -static void -buffer_activate(const std::wstring &name) -{ - auto activate = new Relay::CommandData_BufferActivate(); - activate->buffer_name = name; - relay_send(activate); -} - -static void -buffer_toggle_unimportant(const std::wstring &name) -{ - auto toggle = new Relay::CommandData_BufferToggleUnimportant(); - toggle->buffer_name = name; - relay_send(toggle); -} - -// --- Current buffer ---------------------------------------------------------- - -static void -buffer_toggle_log( - const std::wstring &error, const Relay::ResponseData_BufferLog *response) -{ - if (!response) { - show_error_message(error.c_str()); - return; - } - - std::wstring log; - if (!LibertyXDR::utf8_to_wstring( - response->log.data(), response->log.size(), log)) { - show_error_message(L"Invalid encoding."); - return; - } - - std::wstring filtered; - for (auto wch : log) { - if (wch == L'\n') - filtered += L"\r\n"; - else - filtered += wch; - } - - SetWindowText(g.hwndBufferLog, filtered.c_str()); - ShowWindow(g.hwndBuffer, SW_HIDE); - ShowWindow(g.hwndBufferLog, SW_SHOW); -} - -static void -buffer_toggle_log() -{ - if (IsWindowVisible(g.hwndBufferLog)) { - ShowWindow(g.hwndBufferLog, SW_HIDE); - ShowWindow(g.hwndBuffer, SW_SHOW); - SetWindowText(g.hwndBufferLog, L""); - return; - } - - auto log = new Relay::CommandData_BufferLog(); - log->buffer_name = g.buffer_current; - relay_send(log, [name = g.buffer_current](auto error, auto response) { - if (g.buffer_current != name) - return; - buffer_toggle_log(error, - dynamic_cast<const Relay::ResponseData_BufferLog *>(response)); - }); -} - static bool buffer_at_bottom() { @@ -354,6 +287,7 @@ refresh_icon() if (b.highlighted) icon = g.hiconHighlighted; + // XXX: This may not change the taskbar icon. SendMessage(g.hwndMain, WM_SETICON, ICON_SMALL, (LPARAM) icon); SendMessage(g.hwndMain, WM_SETICON, ICON_BIG, (LPARAM) icon); } @@ -430,6 +364,88 @@ refresh_status() SetWindowText(g.hwndStatus, status.c_str()); } +static void +recheck_highlighted() +{ + // Corresponds to the logic toggling the bool on. + auto b = buffer_by_name(g.buffer_current); + if (b && b->highlighted && buffer_at_bottom() && + !IsIconic(g.hwndMain) && !IsWindowVisible(g.hwndBufferLog)) { + b->highlighted = false; + refresh_icon(); + refresh_buffer_list(); + } +} + +// --- Buffer actions ---------------------------------------------------------- + +static void +buffer_activate(const std::wstring &name) +{ + auto activate = new Relay::CommandData_BufferActivate(); + activate->buffer_name = name; + relay_send(activate); +} + +static void +buffer_toggle_unimportant(const std::wstring &name) +{ + auto toggle = new Relay::CommandData_BufferToggleUnimportant(); + toggle->buffer_name = name; + relay_send(toggle); +} + +static void +buffer_toggle_log( + const std::wstring &error, const Relay::ResponseData_BufferLog *response) +{ + if (!response) { + show_error_message(error.c_str()); + return; + } + + std::wstring log; + if (!LibertyXDR::utf8_to_wstring( + response->log.data(), response->log.size(), log)) { + show_error_message(L"Invalid encoding."); + return; + } + + std::wstring filtered; + for (auto wch : log) { + if (wch == L'\n') + filtered += L"\r\n"; + else + filtered += wch; + } + + SetWindowText(g.hwndBufferLog, filtered.c_str()); + ShowWindow(g.hwndBuffer, SW_HIDE); + ShowWindow(g.hwndBufferLog, SW_SHOW); +} + +static void +buffer_toggle_log() +{ + if (IsWindowVisible(g.hwndBufferLog)) { + ShowWindow(g.hwndBufferLog, SW_HIDE); + ShowWindow(g.hwndBuffer, SW_SHOW); + SetWindowText(g.hwndBufferLog, L""); + + recheck_highlighted(); + return; + } + + auto log = new Relay::CommandData_BufferLog(); + log->buffer_name = g.buffer_current; + relay_send(log, [name = g.buffer_current](auto error, auto response) { + if (g.buffer_current != name) + return; + buffer_toggle_log(error, + dynamic_cast<const Relay::ResponseData_BufferLog *>(response)); + }); +} + // --- Rich Edit formatting ---------------------------------------------------- static COLORREF @@ -695,7 +711,7 @@ buffer_print_line(std::vector<BufferLine>::const_iterator begin, static void buffer_print_separator() { - bool sameline = !GetWindowTextLength(g.hwndBuffer); + bool sameline = !buffer_reset_selection(); CHARFORMAT2 format = default_charformat(); format.dwEffects &= ~CFE_AUTOCOLOR; @@ -728,6 +744,7 @@ refresh_buffer(const Buffer &b) buffer_print_and_watch_trailing_date_changes(); buffer_scroll_to_bottom(); + // We will get a scroll event, so no need to recheck_highlighted() here. SendMessage(g.hwndBuffer, WM_SETREDRAW, (WPARAM) TRUE, 0); InvalidateRect(g.hwndBuffer, NULL, TRUE); @@ -749,8 +766,9 @@ relay_process_buffer_line(Buffer &b, Relay::EventData_BufferLine &m) // Retained mode is complicated. bool display = (!m.is_unimportant || !bc->hide_unimportant) && (b.buffer_name == g.buffer_current || m.leak_to_active); + // XXX: It would be great if it didn't autoscroll when focused. bool to_bottom = display && - buffer_at_bottom(); + (buffer_at_bottom() || GetFocus() == g.hwndBuffer); bool visible = display && to_bottom && !IsIconic(g.hwndMain) && @@ -914,11 +932,11 @@ relay_process_message(const Relay::EventMessage &m) b->buffer_name = data.new_; - refresh_buffer_list(); if (data.buffer_name == g.buffer_current) { g.buffer_current = data.new_; refresh_status(); } + refresh_buffer_list(); if (data.buffer_name == g.buffer_last) g.buffer_last = data.new_; break; @@ -1465,6 +1483,7 @@ richedit_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, { // Dragging the scrollbar doesn't result in EN_VSCROLL. LRESULT lResult = DefSubclassProc(hWnd, uMsg, wParam, lParam); + recheck_highlighted(); refresh_status(); return lResult; } @@ -1522,8 +1541,12 @@ process_resize(UINT w, UINT h) MoveWindow(g.hwndBufferList, 3, top, 150, h - top - bottom, FALSE); MoveWindow(g.hwndBuffer, 156, top, w - 159, h - top - bottom, FALSE); MoveWindow(g.hwndBufferLog, 156, top, w - 159, h - top - bottom, FALSE); - if (to_bottom) + if (to_bottom) { buffer_scroll_to_bottom(); + } else { + recheck_highlighted(); + refresh_status(); + } InvalidateRect(g.hwndMain, NULL, TRUE); } @@ -1685,8 +1708,10 @@ window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } case WM_SYSCOMMAND: { + // We're not deiconified yet, so duplicate recheck_highlighted(). auto b = buffer_by_name(g.buffer_current); - if (b && wParam == SC_RESTORE) { + if (wParam == SC_RESTORE && b && b->highlighted && buffer_at_bottom() && + !IsWindowVisible(g.hwndBufferLog)) { b->highlighted = false; refresh_icon(); } @@ -1694,13 +1719,15 @@ window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) break; } case WM_COMMAND: - if (!lParam) + if (!lParam) { process_accelerator(LOWORD(wParam)); - else if (lParam == (LPARAM) g.hwndBufferList) + } else if (lParam == (LPARAM) g.hwndBufferList) { process_bufferlist_notification(HIWORD(wParam)); - else if (lParam == (LPARAM) g.hwndBuffer && - HIWORD(wParam) == EN_VSCROLL) + } else if (lParam == (LPARAM) g.hwndBuffer && + HIWORD(wParam) == EN_VSCROLL) { + recheck_highlighted(); refresh_status(); + } return 0; case WM_NOTIFY: switch (((LPNMHDR) lParam)->code) { |