From 800810f09288f05e5280dddf8c2d90dd2d580519 Mon Sep 17 00:00:00 2001 From: popaprozac Date: Sat, 26 Apr 2025 18:48:27 -0700 Subject: [PATCH] extract taskbar --- v3/pkg/services/badge/badge_windows.go | 101 ++----------------------- v3/pkg/w32/ole32.go | 24 +++++- v3/pkg/w32/taskbar.go | 95 +++++++++++++++++++++++ 3 files changed, 124 insertions(+), 96 deletions(-) create mode 100644 v3/pkg/w32/taskbar.go diff --git a/v3/pkg/services/badge/badge_windows.go b/v3/pkg/services/badge/badge_windows.go index 0351c6328..52310e91a 100644 --- a/v3/pkg/services/badge/badge_windows.go +++ b/v3/pkg/services/badge/badge_windows.go @@ -9,8 +9,6 @@ import ( "image/color" "image/png" "os" - "syscall" - "unsafe" "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/w32" @@ -19,95 +17,8 @@ import ( "golang.org/x/image/math/fixed" ) -var ( - ole32 = syscall.NewLazyDLL("ole32.dll") - coCreateInstance = ole32.NewProc("CoCreateInstance") -) - -const ( - CLSCTX_INPROC_SERVER = 0x1 -) - -var ( - CLSID_TaskbarList = syscall.GUID{0x56FDF344, 0xFD6D, 0x11D0, [8]byte{0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90}} - IID_ITaskbarList3 = syscall.GUID{0xEA1AFB91, 0x9E28, 0x4B86, [8]byte{0x90, 0xE9, 0x9E, 0x9F, 0x8A, 0x5E, 0xEF, 0xAF}} -) - -type ITaskbarList3 struct { - lpVtbl *taskbarList3Vtbl -} - -type taskbarList3Vtbl struct { - QueryInterface uintptr - AddRef uintptr - Release uintptr - HrInit uintptr - AddTab uintptr - DeleteTab uintptr - ActivateTab uintptr - SetActiveAlt uintptr - MarkFullscreenWindow uintptr - SetProgressValue uintptr - SetProgressState uintptr - RegisterTab uintptr - UnregisterTab uintptr - SetTabOrder uintptr - SetTabActive uintptr - ThumbBarAddButtons uintptr - ThumbBarUpdateButtons uintptr - ThumbBarSetImageList uintptr - SetOverlayIcon uintptr - SetThumbnailTooltip uintptr - SetThumbnailClip uintptr -} - -func newTaskbarList3() (*ITaskbarList3, error) { - const COINIT_APARTMENTTHREADED = 0x2 - - coInit := ole32.NewProc("CoInitializeEx") - if hr, _, _ := coInit.Call(0, COINIT_APARTMENTTHREADED); hr != 0 && hr != 0x1 { - return nil, syscall.Errno(hr) - } - - var taskbar *ITaskbarList3 - hr, _, _ := coCreateInstance.Call( - uintptr(unsafe.Pointer(&CLSID_TaskbarList)), - 0, - uintptr(CLSCTX_INPROC_SERVER), - uintptr(unsafe.Pointer(&IID_ITaskbarList3)), - uintptr(unsafe.Pointer(&taskbar)), - ) - - if hr != 0 { - ole32.NewProc("CoUninitialize").Call() - return nil, syscall.Errno(hr) - } - - if r, _, _ := syscall.SyscallN(taskbar.lpVtbl.HrInit, uintptr(unsafe.Pointer(taskbar))); r != 0 { - syscall.SyscallN(taskbar.lpVtbl.Release, uintptr(unsafe.Pointer(taskbar))) - ole32.NewProc("CoUninitialize").Call() - return nil, syscall.Errno(r) - } - - return taskbar, nil -} - -func (t *ITaskbarList3) SetOverlayIcon(hwnd syscall.Handle, hIcon syscall.Handle, description *uint16) error { - ret, _, _ := syscall.SyscallN( - t.lpVtbl.SetOverlayIcon, - uintptr(unsafe.Pointer(t)), - uintptr(hwnd), - uintptr(hIcon), - uintptr(unsafe.Pointer(description)), - ) - if ret != 0 { - return syscall.Errno(ret) - } - return nil -} - type windowsBadge struct { - taskbar *ITaskbarList3 + taskbar *w32.ITaskbarList3 badgeImg *image.RGBA badgeSize int fontManager *FontManager @@ -147,7 +58,7 @@ func NewWithOptions(options Options) *Service { } func (w *windowsBadge) Startup(ctx context.Context, options application.ServiceOptions) error { - taskbar, err := newTaskbarList3() + taskbar, err := w32.NewTaskbarList3() if err != nil { return err } @@ -159,8 +70,8 @@ func (w *windowsBadge) Startup(ctx context.Context, options application.ServiceO func (w *windowsBadge) Shutdown() error { if w.taskbar != nil { - syscall.SyscallN(w.taskbar.lpVtbl.Release, uintptr(unsafe.Pointer(w.taskbar))) - ole32.NewProc("CoUninitialize").Call() + w.taskbar.Release() + w32.CoUninitialize() } return nil @@ -202,7 +113,7 @@ func (w *windowsBadge) SetBadge(label string) error { } defer w32.DestroyIcon(hicon) - return w.taskbar.SetOverlayIcon(syscall.Handle(hwnd), syscall.Handle(hicon), nil) + return w.taskbar.SetOverlayIcon(hwnd, hicon, nil) } func (w *windowsBadge) RemoveBadge() error { @@ -225,7 +136,7 @@ func (w *windowsBadge) RemoveBadge() error { return err } - return w.taskbar.SetOverlayIcon(syscall.Handle(hwnd), 0, nil) + return w.taskbar.SetOverlayIcon(hwnd, 0, nil) } func (w *windowsBadge) createBadgeIcon() (w32.HICON, error) { diff --git a/v3/pkg/w32/ole32.go b/v3/pkg/w32/ole32.go index 8e7c20d4a..a7d204912 100644 --- a/v3/pkg/w32/ole32.go +++ b/v3/pkg/w32/ole32.go @@ -7,9 +7,10 @@ package w32 import ( - "github.com/wailsapp/go-webview2/pkg/combridge" "syscall" "unsafe" + + "github.com/wailsapp/go-webview2/pkg/combridge" ) var ( @@ -19,6 +20,7 @@ var ( procCoInitialize = modole32.NewProc("CoInitialize") procOleInitialize = modole32.NewProc("OleInitialize") procCoUninitialize = modole32.NewProc("CoUninitialize") + procCoCreateInstance = modole32.NewProc("CoCreateInstance") procCreateStreamOnHGlobal = modole32.NewProc("CreateStreamOnHGlobal") procRegisterDragDrop = modole32.NewProc("RegisterDragDrop") procRevokeDragDrop = modole32.NewProc("RevokeDragDrop") @@ -49,6 +51,26 @@ func CoUninitialize() { procCoUninitialize.Call() } +func CoCreateInstance(clsid *syscall.GUID, dwClsContext uintptr, riid *syscall.GUID, ppv uintptr) HRESULT { + ret, _, _ := procCoCreateInstance.Call( + uintptr(unsafe.Pointer(clsid)), + 0, + uintptr(dwClsContext), + uintptr(unsafe.Pointer(riid)), + uintptr(ppv)) + + switch uint32(ret) { + case E_INVALIDARG: + panic("CoCreateInstance failed with E_INVALIDARG") + case E_OUTOFMEMORY: + panic("CoCreateInstance failed with E_OUTOFMEMORY") + case E_UNEXPECTED: + panic("CoCreateInstance failed with E_UNEXPECTED") + } + + return HRESULT(ret) +} + func CreateStreamOnHGlobal(hGlobal HGLOBAL, fDeleteOnRelease bool) *IStream { stream := new(IStream) ret, _, _ := procCreateStreamOnHGlobal.Call( diff --git a/v3/pkg/w32/taskbar.go b/v3/pkg/w32/taskbar.go new file mode 100644 index 000000000..7f2a697d6 --- /dev/null +++ b/v3/pkg/w32/taskbar.go @@ -0,0 +1,95 @@ +//go:build windows + +package w32 + +import ( + "syscall" + "unsafe" +) + +var ( + CLSID_TaskbarList = syscall.GUID{Data1: 0x56FDF344, Data2: 0xFD6D, Data3: 0x11D0, Data4: [8]byte{0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90}} + IID_ITaskbarList3 = syscall.GUID{Data1: 0xEA1AFB91, Data2: 0x9E28, Data3: 0x4B86, Data4: [8]byte{0x90, 0xE9, 0x9E, 0x9F, 0x8A, 0x5E, 0xEF, 0xAF}} +) + +// ITaskbarList3 interface for Windows taskbar functionality +type ITaskbarList3 struct { + lpVtbl *taskbarList3Vtbl +} + +type taskbarList3Vtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr + HrInit uintptr + AddTab uintptr + DeleteTab uintptr + ActivateTab uintptr + SetActiveAlt uintptr + MarkFullscreenWindow uintptr + SetProgressValue uintptr + SetProgressState uintptr + RegisterTab uintptr + UnregisterTab uintptr + SetTabOrder uintptr + SetTabActive uintptr + ThumbBarAddButtons uintptr + ThumbBarUpdateButtons uintptr + ThumbBarSetImageList uintptr + SetOverlayIcon uintptr + SetThumbnailTooltip uintptr + SetThumbnailClip uintptr +} + +// NewTaskbarList3 creates a new instance of ITaskbarList3 +func NewTaskbarList3() (*ITaskbarList3, error) { + const COINIT_APARTMENTTHREADED = 0x2 + + if hrInit := CoInitializeEx(COINIT_APARTMENTTHREADED); hrInit != 0 && hrInit != 0x1 { + return nil, syscall.Errno(hrInit) + } + + var taskbar *ITaskbarList3 + hr := CoCreateInstance( + &CLSID_TaskbarList, + CLSCTX_INPROC_SERVER, + &IID_ITaskbarList3, + uintptr(unsafe.Pointer(&taskbar)), + ) + + if hr != 0 { + CoUninitialize() + return nil, syscall.Errno(hr) + } + + if r, _, _ := syscall.SyscallN(taskbar.lpVtbl.HrInit, uintptr(unsafe.Pointer(taskbar))); r != 0 { + syscall.SyscallN(taskbar.lpVtbl.Release, uintptr(unsafe.Pointer(taskbar))) + CoUninitialize() + return nil, syscall.Errno(r) + } + + return taskbar, nil +} + +// SetOverlayIcon sets an overlay icon on the taskbar +func (t *ITaskbarList3) SetOverlayIcon(hwnd HWND, hIcon HICON, description *uint16) error { + ret, _, _ := syscall.SyscallN( + t.lpVtbl.SetOverlayIcon, + uintptr(unsafe.Pointer(t)), + uintptr(hwnd), + uintptr(hIcon), + uintptr(unsafe.Pointer(description)), + ) + if ret != 0 { + return syscall.Errno(ret) + } + return nil +} + +// Release releases the ITaskbarList3 interface +func (t *ITaskbarList3) Release() { + if t != nil { + syscall.SyscallN(t.lpVtbl.Release, uintptr(unsafe.Pointer(t))) + CoUninitialize() + } +}