From aedaa32de43aa41512e9ae3910a53a5edfee45b1 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 7 Aug 2025 18:30:03 -0700 Subject: [PATCH] add Windows taskbar control buttons (TODO: icons) --- backend/app.go | 36 +++++++--- backend/{ => windows}/smtc.go | 2 +- backend/{ => windows}/smtc_cfuncs.go | 2 +- backend/{ => windows}/smtc_unsupported.go | 2 +- backend/windows/taskbar_buttons.c | 69 +++++++++++++++++++ backend/windows/taskbar_buttons.go | 29 ++++++++ backend/windows/taskbar_buttons.h | 13 ++++ backend/windows/taskbar_buttons_consts.go | 9 +++ .../windows/taskbar_buttons_unsupported.go | 9 +++ main.go | 1 + 10 files changed, 158 insertions(+), 14 deletions(-) rename backend/{ => windows}/smtc.go (99%) rename backend/{ => windows}/smtc_cfuncs.go (92%) rename backend/{ => windows}/smtc_unsupported.go (98%) create mode 100644 backend/windows/taskbar_buttons.c create mode 100644 backend/windows/taskbar_buttons.go create mode 100644 backend/windows/taskbar_buttons.h create mode 100644 backend/windows/taskbar_buttons_consts.go create mode 100644 backend/windows/taskbar_buttons_unsupported.go diff --git a/backend/app.go b/backend/app.go index 6597581..ef840bc 100644 --- a/backend/app.go +++ b/backend/app.go @@ -20,6 +20,7 @@ import ( "github.com/dweymouth/supersonic/backend/player" "github.com/dweymouth/supersonic/backend/player/mpv" "github.com/dweymouth/supersonic/backend/util" + "github.com/dweymouth/supersonic/backend/windows" "github.com/google/uuid" "github.com/20after4/configdir" @@ -49,7 +50,7 @@ type App struct { LocalPlayer *mpv.Player UpdateChecker UpdateChecker MPRISHandler *MPRISHandler - WinSMTC *SMTC + WinSMTC *windows.SMTC ipcServer ipc.IPCServer LrcLibFetcher *LrcLibFetcher @@ -378,7 +379,7 @@ func (a *App) setupMPRIS(mprisAppName string) { } func (a *App) SetupWindowsSMTC(hwnd uintptr) { - smtc, err := InitSMTCForWindow(hwnd) + smtc, err := windows.InitSMTCForWindow(hwnd) if err != nil { log.Printf("error initializing SMTC: %d", err) return @@ -386,17 +387,17 @@ func (a *App) SetupWindowsSMTC(hwnd uintptr) { a.WinSMTC = smtc smtc.UpdateMetadata(a.displayAppName, "") - smtc.OnButtonPressed(func(btn SMTCButton) { + smtc.OnButtonPressed(func(btn windows.SMTCButton) { switch btn { - case SMTCButtonPlay: + case windows.SMTCButtonPlay: a.PlaybackManager.Continue() - case SMTCButtonPause: + case windows.SMTCButtonPause: a.PlaybackManager.Pause() - case SMTCButtonNext: + case windows.SMTCButtonNext: a.PlaybackManager.SeekNext() - case SMTCButtonPrevious: + case windows.SMTCButtonPrevious: a.PlaybackManager.SeekBackOrPrevious() - case SMTCButtonStop: + case windows.SMTCButtonStop: a.PlaybackManager.Stop() } }) @@ -425,15 +426,28 @@ func (a *App) SetupWindowsSMTC(hwnd uintptr) { }) a.PlaybackManager.OnPlaying(func() { smtc.SetEnabled(true) - smtc.UpdatePlaybackState(SMTCPlaybackStatePlaying) + smtc.UpdatePlaybackState(windows.SMTCPlaybackStatePlaying) }) a.PlaybackManager.OnPaused(func() { smtc.SetEnabled(true) - smtc.UpdatePlaybackState(SMTCPlaybackStatePaused) + smtc.UpdatePlaybackState(windows.SMTCPlaybackStatePaused) }) a.PlaybackManager.OnStopped(func() { smtc.SetEnabled(false) - smtc.UpdatePlaybackState(SMTCPlaybackStateStopped) + smtc.UpdatePlaybackState(windows.SMTCPlaybackStateStopped) + }) +} + +func (a *App) SetupWindowsTaskbarButtons(hwnd uintptr) { + windows.InitializeTaskbarButtons(hwnd, func(btn windows.TaskbarButton) { + switch btn { + case windows.TaskbarButtonPrevious: + a.PlaybackManager.SeekBackOrPrevious() + case windows.TaskbarButtonPlayPause: + a.PlaybackManager.PlayPause() + case windows.TaskbarButtonNext: + a.PlaybackManager.SeekNext() + } }) } diff --git a/backend/smtc.go b/backend/windows/smtc.go similarity index 99% rename from backend/smtc.go rename to backend/windows/smtc.go index 0c00373..182af90 100644 --- a/backend/smtc.go +++ b/backend/windows/smtc.go @@ -1,6 +1,6 @@ //go:build windows -package backend +package windows /* #cgo CFLAGS: -I . diff --git a/backend/smtc_cfuncs.go b/backend/windows/smtc_cfuncs.go similarity index 92% rename from backend/smtc_cfuncs.go rename to backend/windows/smtc_cfuncs.go index 9ccd949..cb3489e 100644 --- a/backend/smtc_cfuncs.go +++ b/backend/windows/smtc_cfuncs.go @@ -1,6 +1,6 @@ //go:build windows -package backend +package windows /* void btn_callback_cgo(int in) { diff --git a/backend/smtc_unsupported.go b/backend/windows/smtc_unsupported.go similarity index 98% rename from backend/smtc_unsupported.go rename to backend/windows/smtc_unsupported.go index 18cf09e..e00f7d3 100644 --- a/backend/smtc_unsupported.go +++ b/backend/windows/smtc_unsupported.go @@ -1,6 +1,6 @@ //go:build !windows -package backend +package windows import "errors" diff --git a/backend/windows/taskbar_buttons.c b/backend/windows/taskbar_buttons.c new file mode 100644 index 0000000..860176f --- /dev/null +++ b/backend/windows/taskbar_buttons.c @@ -0,0 +1,69 @@ +//go:build windows + +#include +#include +#include +#include +#include "taskbar_buttons.h" + +DEFINE_GUID(IID_ITaskbarList3, + 0xEA1AFB91, 0x9E28, 0x4B86, 0x90, 0xE9, 0x9E, 0x9F, 0x8A, 0x5E, 0xEF, 0xAF); + +static ITaskbarList3 *g_taskbar = NULL; +static ThumbnailCallback g_callback = NULL; +static HWND g_mainHWnd = NULL; +static WNDPROC g_originalProc = NULL; + +THUMBBUTTON g_thumbButtons[3]; + +LRESULT CALLBACK OverrideWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if (msg == WM_COMMAND) { + int buttonId = LOWORD(wParam); + if (g_callback) { + g_callback(buttonId); + return 0; + } + } + + return CallWindowProc(g_originalProc, hwnd, msg, wParam, lParam); +} + +void hook_window_proc(HWND hwnd) { + g_originalProc = (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)OverrideWndProc); +} + +int initialize_taskbar_buttons(void *hwndPtr, ThumbnailCallback cb) { + g_callback = cb; + g_mainHWnd = (HWND)hwndPtr; + hook_window_proc(g_mainHWnd); + + CoInitialize(NULL); + CoCreateInstance(&CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, &IID_ITaskbarList3, (void**)&g_taskbar); + + if (g_taskbar) { + g_taskbar->lpVtbl->HrInit(g_taskbar); + } + + if (!g_taskbar || !g_mainHWnd) return -1; + + ZeroMemory(g_thumbButtons, sizeof(g_thumbButtons)); + + g_thumbButtons[0].dwMask = THB_FLAGS | THB_TOOLTIP; + g_thumbButtons[0].iId = 1; + g_thumbButtons[0].dwFlags = THBF_ENABLED; + wcscpy_s(g_thumbButtons[0].szTip, ARRAYSIZE(g_thumbButtons[0].szTip), L"Previous"); + + g_thumbButtons[1].dwMask = THB_FLAGS | THB_TOOLTIP; + g_thumbButtons[1].iId = 2; + g_thumbButtons[1].dwFlags = THBF_ENABLED; + wcscpy_s(g_thumbButtons[1].szTip, ARRAYSIZE(g_thumbButtons[1].szTip), L"Play"); + + g_thumbButtons[2].dwMask = THB_FLAGS | THB_TOOLTIP; + g_thumbButtons[2].iId = 3; + g_thumbButtons[2].dwFlags = THBF_ENABLED; + wcscpy_s(g_thumbButtons[2].szTip, ARRAYSIZE(g_thumbButtons[2].szTip), L"Next"); + + g_taskbar->lpVtbl->ThumbBarAddButtons(g_taskbar, g_mainHWnd, ARRAYSIZE(g_thumbButtons), g_thumbButtons); + + return 0; +} \ No newline at end of file diff --git a/backend/windows/taskbar_buttons.go b/backend/windows/taskbar_buttons.go new file mode 100644 index 0000000..bcea6d2 --- /dev/null +++ b/backend/windows/taskbar_buttons.go @@ -0,0 +1,29 @@ +//go:build windows + +package windows + +/* +#cgo LDFLAGS: -lole32 +#include "taskbar_buttons.h" + +extern void goButtonClicked(int); +*/ +import "C" +import ( + "unsafe" +) + +var gTaskbarButtonCallback func(TaskbarButton) + +func InitializeTaskbarButtons(hwnd uintptr, callback func(TaskbarButton)) error { + gTaskbarButtonCallback = callback + C.initialize_taskbar_buttons(unsafe.Pointer(hwnd), C.ThumbnailCallback(C.goButtonClicked)) + return nil +} + +//export goButtonClicked +func goButtonClicked(buttonID C.int) { + if gTaskbarButtonCallback != nil { + gTaskbarButtonCallback(TaskbarButton(buttonID)) + } +} diff --git a/backend/windows/taskbar_buttons.h b/backend/windows/taskbar_buttons.h new file mode 100644 index 0000000..eb37a42 --- /dev/null +++ b/backend/windows/taskbar_buttons.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*ThumbnailCallback)(int buttonId); + +int initialize_taskbar_buttons(void *hwndPtr, ThumbnailCallback cb); + +#ifdef __cplusplus +} +#endif diff --git a/backend/windows/taskbar_buttons_consts.go b/backend/windows/taskbar_buttons_consts.go new file mode 100644 index 0000000..d6b2050 --- /dev/null +++ b/backend/windows/taskbar_buttons_consts.go @@ -0,0 +1,9 @@ +package windows + +type TaskbarButton int + +const ( + TaskbarButtonPrevious TaskbarButton = 1 + TaskbarButtonPlayPause TaskbarButton = 2 + TaskbarButtonNext TaskbarButton = 3 +) diff --git a/backend/windows/taskbar_buttons_unsupported.go b/backend/windows/taskbar_buttons_unsupported.go new file mode 100644 index 0000000..0a451fd --- /dev/null +++ b/backend/windows/taskbar_buttons_unsupported.go @@ -0,0 +1,9 @@ +//go:build !windows + +package windows + +import "errors" + +func InitializeTaskbarButtons(hwnd uintptr, callback func(TaskbarButton)) error { + return errors.New("taskbar buttons unsupported") +} diff --git a/main.go b/main.go index 25e8563..4151c61 100644 --- a/main.go +++ b/main.go @@ -97,6 +97,7 @@ func main() { if runtime.GOOS == "windows" { hwnd := ctx.(driver.WindowsWindowContext).HWND myApp.SetupWindowsSMTC(hwnd) + myApp.SetupWindowsTaskbarButtons(hwnd) } }) }