add Windows taskbar control buttons (TODO: icons)

This commit is contained in:
Drew Weymouth
2025-08-08 19:15:46 -07:00
committed by Drew Weymouth
parent 21edd38f38
commit aedaa32de4
10 changed files with 158 additions and 14 deletions
+25 -11
View File
@@ -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()
}
})
}
+1 -1
View File
@@ -1,6 +1,6 @@
//go:build windows
package backend
package windows
/*
#cgo CFLAGS: -I .
@@ -1,6 +1,6 @@
//go:build windows
package backend
package windows
/*
void btn_callback_cgo(int in) {
@@ -1,6 +1,6 @@
//go:build !windows
package backend
package windows
import "errors"
+69
View File
@@ -0,0 +1,69 @@
//go:build windows
#include <windows.h>
#include <shobjidl.h>
#include <initguid.h>
#include <stdio.h>
#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;
}
+29
View File
@@ -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))
}
}
+13
View File
@@ -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
@@ -0,0 +1,9 @@
package windows
type TaskbarButton int
const (
TaskbarButtonPrevious TaskbarButton = 1
TaskbarButtonPlayPause TaskbarButton = 2
TaskbarButtonNext TaskbarButton = 3
)
@@ -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")
}
+1
View File
@@ -97,6 +97,7 @@ func main() {
if runtime.GOOS == "windows" {
hwnd := ctx.(driver.WindowsWindowContext).HWND
myApp.SetupWindowsSMTC(hwnd)
myApp.SetupWindowsTaskbarButtons(hwnd)
}
})
}