add Windows taskbar control buttons (TODO: icons)
This commit is contained in:
committed by
Drew Weymouth
parent
21edd38f38
commit
aedaa32de4
@@ -0,0 +1,207 @@
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I .
|
||||
void btn_callback_cgo(int in);
|
||||
void seek_callback_cgo(int in);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type SMTCPlaybackState int
|
||||
type SMTCButton int
|
||||
|
||||
const (
|
||||
// constants from smtc.h in github.com/supersonic-app/smtc-dll
|
||||
SMTCPlaybackStateStopped SMTCPlaybackState = 2
|
||||
SMTCPlaybackStatePlaying SMTCPlaybackState = 3
|
||||
SMTCPlaybackStatePaused SMTCPlaybackState = 4
|
||||
|
||||
SMTCButtonPlay SMTCButton = 0
|
||||
SMTCButtonPause SMTCButton = 1
|
||||
SMTCButtonStop SMTCButton = 2
|
||||
SMTCButtonPrevious SMTCButton = 4
|
||||
SMTCButtonNext SMTCButton = 5
|
||||
)
|
||||
|
||||
type SMTC struct {
|
||||
dll *windows.DLL
|
||||
|
||||
onButtonPressed func(SMTCButton)
|
||||
onSeek func(int)
|
||||
}
|
||||
|
||||
var smtcInstance *SMTC
|
||||
|
||||
func InitSMTCForWindow(hwnd uintptr) (*SMTC, error) {
|
||||
if maj, _, _ := windows.RtlGetNtVersionNumbers(); maj < 10 {
|
||||
return nil, errors.New("SMTC is not supported on Windows versions < 10")
|
||||
}
|
||||
|
||||
dll, err := windows.LoadDLL("smtc.dll")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proc, err := dll.FindProc("InitializeForWindow")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hr, _, _ := proc.Call(hwnd, uintptr(unsafe.Pointer(C.btn_callback_cgo)), uintptr(unsafe.Pointer(C.seek_callback_cgo)))
|
||||
if hr < 0 {
|
||||
return nil, fmt.Errorf("InitializeForWindow failed with HRESULT=%d", hr)
|
||||
}
|
||||
|
||||
smtcInstance = &SMTC{dll: dll}
|
||||
return smtcInstance, nil
|
||||
}
|
||||
|
||||
func (s *SMTC) OnButtonPressed(f func(SMTCButton)) {
|
||||
s.onButtonPressed = f
|
||||
}
|
||||
|
||||
func (s *SMTC) OnSeek(f func(millis int)) {
|
||||
s.onSeek = f
|
||||
}
|
||||
|
||||
func (s *SMTC) Shutdown() {
|
||||
if s.dll == nil {
|
||||
return
|
||||
}
|
||||
proc, err := s.dll.FindProc("Destroy")
|
||||
if err == nil {
|
||||
proc.Call()
|
||||
}
|
||||
|
||||
s.dll.Release()
|
||||
s.dll = nil
|
||||
smtcInstance = nil
|
||||
}
|
||||
|
||||
func (s *SMTC) UpdatePlaybackState(state SMTCPlaybackState) error {
|
||||
if s.dll == nil {
|
||||
return errors.New("SMTC DLL not available")
|
||||
}
|
||||
|
||||
proc, err := s.dll.FindProc("SetPlaybackState")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hr, _, _ := proc.Call(uintptr(state)); hr < 0 {
|
||||
return fmt.Errorf("SetPlaybackState failed with HRESULT=%d", hr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SMTC) UpdateMetadata(title, artist string) error {
|
||||
if s.dll == nil {
|
||||
return errors.New("SMTC DLL not available")
|
||||
}
|
||||
|
||||
utfTitle, err := windows.UTF16PtrFromString(title)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utfArtist, err := windows.UTF16PtrFromString(artist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proc, err := s.dll.FindProc("SetMetadata")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hr, _, _ := proc.Call(uintptr(unsafe.Pointer(utfTitle)), uintptr(unsafe.Pointer(utfArtist)))
|
||||
if hr < 0 {
|
||||
return fmt.Errorf("SetMetadata failed with HRESULT=%d", hr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SMTC) UpdatePosition(positionMillis, durationMillis int) error {
|
||||
if s.dll == nil {
|
||||
return errors.New("SMTC DLL not available")
|
||||
}
|
||||
|
||||
proc, err := s.dll.FindProc("SetPosition")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hr, _, _ := proc.Call(uintptr(positionMillis), uintptr(durationMillis))
|
||||
if hr < 0 {
|
||||
return fmt.Errorf("SetPosition failed with HRESULT=%d", hr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SMTC) SetThumbnail(filepath string) error {
|
||||
if s.dll == nil {
|
||||
return errors.New("SMTC DLL not available")
|
||||
}
|
||||
|
||||
proc, err := s.dll.FindProc("SetThumbnailPath")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utfPath, err := windows.UTF16PtrFromString(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hr, _, _ := proc.Call(uintptr(unsafe.Pointer(utfPath)))
|
||||
if hr < 0 {
|
||||
return fmt.Errorf("SetThumbnailPath failed with HRESULT=%d", hr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SMTC) SetEnabled(enabled bool) error {
|
||||
if s.dll == nil {
|
||||
return errors.New("SMTC DLL not available")
|
||||
}
|
||||
|
||||
proc, err := s.dll.FindProc("SetEnabled")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var arg uintptr = 0
|
||||
if enabled {
|
||||
arg = 1
|
||||
}
|
||||
|
||||
hr, _, _ := proc.Call(arg)
|
||||
if hr < 0 {
|
||||
return fmt.Errorf("SetEnabled failed with HRESULT=%d", hr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//export btnCallback
|
||||
func btnCallback(in int) {
|
||||
if smtcInstance != nil && smtcInstance.onButtonPressed != nil {
|
||||
smtcInstance.onButtonPressed(SMTCButton(in))
|
||||
}
|
||||
}
|
||||
|
||||
//export seekCallback
|
||||
func seekCallback(millis int) {
|
||||
if smtcInstance != nil && smtcInstance.onSeek != nil {
|
||||
smtcInstance.onSeek(millis)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
/*
|
||||
void btn_callback_cgo(int in) {
|
||||
void btnCallback(int);
|
||||
btnCallback(in);
|
||||
}
|
||||
|
||||
void seek_callback_cgo(int in) {
|
||||
void seekCallback(int);
|
||||
seekCallback(in);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
@@ -0,0 +1,55 @@
|
||||
//go:build !windows
|
||||
|
||||
package windows
|
||||
|
||||
import "errors"
|
||||
|
||||
type SMTCPlaybackState int
|
||||
type SMTCButton int
|
||||
|
||||
const (
|
||||
// constants from smtc.h in github.com/supersonic-app/smtc-dll
|
||||
SMTCPlaybackStateStopped SMTCPlaybackState = 2
|
||||
SMTCPlaybackStatePlaying SMTCPlaybackState = 3
|
||||
SMTCPlaybackStatePaused SMTCPlaybackState = 4
|
||||
|
||||
SMTCButtonPlay SMTCButton = 0
|
||||
SMTCButtonPause SMTCButton = 1
|
||||
SMTCButtonStop SMTCButton = 2
|
||||
SMTCButtonPrevious SMTCButton = 4
|
||||
SMTCButtonNext SMTCButton = 5
|
||||
)
|
||||
|
||||
type SMTC struct{}
|
||||
|
||||
var smtcUnsupportedErr = errors.New("SMTC is not supported on this platformo")
|
||||
|
||||
func InitSMTCForWindow(hwnd uintptr) (*SMTC, error) {
|
||||
return nil, smtcUnsupportedErr
|
||||
}
|
||||
|
||||
func (s *SMTC) SetEnabled(enabled bool) error {
|
||||
return smtcUnsupportedErr
|
||||
}
|
||||
|
||||
func (s *SMTC) SetThumbnail(filepath string) error {
|
||||
return smtcUnsupportedErr
|
||||
}
|
||||
|
||||
func (s *SMTC) OnButtonPressed(func(SMTCButton)) {}
|
||||
|
||||
func (s *SMTC) OnSeek(f func(millis int)) {}
|
||||
|
||||
func (s *SMTC) Shutdown() {}
|
||||
|
||||
func (s *SMTC) UpdatePlaybackState(state SMTCPlaybackState) error {
|
||||
return smtcUnsupportedErr
|
||||
}
|
||||
|
||||
func (s *SMTC) UpdateMetadata(title, artist string) error {
|
||||
return smtcUnsupportedErr
|
||||
}
|
||||
|
||||
func (s *SMTC) UpdatePosition(positionMillis, durationMillis int) error {
|
||||
return smtcUnsupportedErr
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user