//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); #define WM_SET_PLAYING_STATE (WM_APP + 1) static ITaskbarList3 *g_taskbar = NULL; static ThumbnailCallback g_callback = NULL; static HWND g_mainHWnd = NULL; static WNDPROC g_originalProc = NULL; THUMBBUTTON g_thumbButtons[3]; static HICON g_prevIcon; static HICON g_nextIcon; static HICON g_playIcon; static HICON g_pauseIcon; static wchar_t g_tooltipPlay[64] = {0}; static wchar_t g_tooltipPause[64] = {0}; static wchar_t g_tooltipPrev[64] = {0}; static wchar_t g_tooltipNext[64] = {0}; static void utf8_to_utf16(const char* utf8, wchar_t* utf16buf, size_t maxChars) { MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16buf, (int)maxChars); } void set_tooltips_utf8(const char* prev, const char* next, const char* play, const char* pause) { utf8_to_utf16(play, g_tooltipPlay, _countof(g_tooltipPlay)); utf8_to_utf16(pause, g_tooltipPause, _countof(g_tooltipPause)); utf8_to_utf16(prev, g_tooltipPrev, _countof(g_tooltipPrev)); utf8_to_utf16(next, g_tooltipNext, _countof(g_tooltipNext)); } static HICON create_icon_from_bgra(const void *bgra, int width, int height) { HICON hIcon = NULL; // Create mask bitmap (not used, but required) HBITMAP hMonoMask = CreateBitmap(width, height, 1, 1, NULL); // Create color bitmap from provided pixels BITMAPV5HEADER bi; ZeroMemory(&bi, sizeof(bi)); bi.bV5Size = sizeof(bi); bi.bV5Width = width; bi.bV5Height = -height; // top-down DIB bi.bV5Planes = 1; bi.bV5BitCount = 32; bi.bV5Compression = BI_BITFIELDS; bi.bV5RedMask = 0x00FF0000; bi.bV5GreenMask = 0x0000FF00; bi.bV5BlueMask = 0x000000FF; bi.bV5AlphaMask = 0xFF000000; void *bits = NULL; HDC hdc = GetDC(NULL); HBITMAP hBmp = CreateDIBSection(hdc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, &bits, NULL, 0); ReleaseDC(NULL, hdc); if (hBmp && bits) { memcpy(bits, bgra, width * height * 4); ICONINFO ii; ZeroMemory(&ii, sizeof(ii)); ii.fIcon = TRUE; ii.hbmMask = hMonoMask; ii.hbmColor = hBmp; hIcon = CreateIconIndirect(&ii); DeleteObject(hBmp); DeleteObject(hMonoMask); } return hIcon; } int initialize_taskbar_icons(const void *bgraPrev, const void *bgraNext, const void *bgraPlay, const void *bgraPause, int width, int height) { g_prevIcon = create_icon_from_bgra(bgraPrev, width, height); g_nextIcon = create_icon_from_bgra(bgraNext, width, height); g_playIcon = create_icon_from_bgra(bgraPlay, width, height); g_pauseIcon = create_icon_from_bgra(bgraPause, width, height); return 0; } 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; } } else if (msg == WM_SET_PLAYING_STATE) { if (g_taskbar && g_mainHWnd && g_playIcon && g_pauseIcon) { int playing = LOWORD(wParam) ? 1 : 0; g_thumbButtons[1].hIcon = playing ? g_pauseIcon : g_playIcon; wcscpy_s(g_thumbButtons[1].szTip, ARRAYSIZE(g_thumbButtons[1].szTip), playing ? g_tooltipPause : g_tooltipPlay); g_taskbar->lpVtbl->ThumbBarUpdateButtons( g_taskbar, g_mainHWnd, ARRAYSIZE(g_thumbButtons), g_thumbButtons ); } return 0; } return CallWindowProc(g_originalProc, hwnd, msg, wParam, lParam); } int set_is_playing(int playing) { if (g_mainHWnd) { PostMessage(g_mainHWnd, WM_SET_PLAYING_STATE, (WPARAM)playing, 0); return 0; } return -1; } int initialize_taskbar_buttons(void *hwndPtr, ThumbnailCallback cb) { g_callback = cb; g_mainHWnd = (HWND)hwndPtr; // subclass the WndProc with our OverrideWndProc g_originalProc = (WNDPROC)SetWindowLongPtr(g_mainHWnd, GWLP_WNDPROC, (LONG_PTR)OverrideWndProc); 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), g_tooltipPrev); if (g_prevIcon) { g_thumbButtons[0].dwMask |= THB_ICON; g_thumbButtons[0].hIcon = g_prevIcon; } 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), g_tooltipPlay); if (g_playIcon) { g_thumbButtons[1].dwMask |= THB_ICON; g_thumbButtons[1].hIcon = g_playIcon; } 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), g_tooltipNext); if (g_nextIcon) { g_thumbButtons[2].dwMask |= THB_ICON; g_thumbButtons[2].hIcon = g_nextIcon; } g_taskbar->lpVtbl->ThumbBarAddButtons(g_taskbar, g_mainHWnd, ARRAYSIZE(g_thumbButtons), g_thumbButtons); return 0; }