Initial plan: Fix Windows-specific bugs from discussion #5001

Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-02-22 05:22:19 +00:00
commit ff4e749ce6
39 changed files with 15198 additions and 0 deletions

View file

@ -0,0 +1,174 @@
package application
import (
"slices"
"github.com/wailsapp/wails/v3/pkg/events"
)
// EventManager manages event-related operations
type EventManager struct {
app *App
}
// newEventManager creates a new EventManager instance
func newEventManager(app *App) *EventManager {
return &EventManager{
app: app,
}
}
// Emit emits a custom event with the specified name and associated data.
// It returns a boolean indicating whether the event was cancelled by a hook.
//
// If no data argument is provided, Emit emits an event with nil data.
// When there is exactly one data argument, it will be used as the custom event's data field.
// When more than one argument is provided, the event's data field will be set to the argument slice.
//
// If the given event name is registered, Emit validates the data parameter
// against the expected data type. In case of a mismatch, Emit reports an error
// to the registered error handler for the application and cancels the event.
func (em *EventManager) Emit(name string, data ...any) bool {
event := &CustomEvent{Name: name}
if len(data) == 1 {
event.Data = data[0]
} else if len(data) > 1 {
event.Data = data
}
if err := em.app.customEventProcessor.Emit(event); err != nil {
globalApplication.handleError(err)
}
return event.IsCancelled()
}
// EmitEvent emits a custom event object (internal use)
// It returns a boolean indicating whether the event was cancelled by a hook.
//
// If the given event name is registered, emitEvent validates the data parameter
// against the expected data type. In case of a mismatch, emitEvent reports an error
// to the registered error handler for the application and cancels the event.
func (em *EventManager) EmitEvent(event *CustomEvent) bool {
if err := em.app.customEventProcessor.Emit(event); err != nil {
globalApplication.handleError(err)
}
return event.IsCancelled()
}
// On registers a listener for custom events
func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() {
return em.app.customEventProcessor.On(name, callback)
}
// Off removes all listeners for a custom event
func (em *EventManager) Off(name string) {
em.app.customEventProcessor.Off(name)
}
// OnMultiple registers a listener for custom events that will be called N times
func (em *EventManager) OnMultiple(name string, callback func(event *CustomEvent), counter int) {
em.app.customEventProcessor.OnMultiple(name, callback, counter)
}
// Reset removes all custom event listeners
func (em *EventManager) Reset() {
em.app.customEventProcessor.OffAll()
}
// OnApplicationEvent registers a listener for application events
func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() {
eventID := uint(eventType)
em.app.applicationEventListenersLock.Lock()
defer em.app.applicationEventListenersLock.Unlock()
listener := &EventListener{
callback: callback,
}
em.app.applicationEventListeners[eventID] = append(em.app.applicationEventListeners[eventID], listener)
if em.app.impl != nil {
go func() {
defer handlePanic()
em.app.impl.on(eventID)
}()
}
return func() {
// lock the map
em.app.applicationEventListenersLock.Lock()
defer em.app.applicationEventListenersLock.Unlock()
// Remove listener
em.app.applicationEventListeners[eventID] = slices.DeleteFunc(em.app.applicationEventListeners[eventID], func(l *EventListener) bool {
return l == listener
})
}
}
// RegisterApplicationEventHook registers an application event hook
func (em *EventManager) RegisterApplicationEventHook(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() {
eventID := uint(eventType)
em.app.applicationEventHooksLock.Lock()
defer em.app.applicationEventHooksLock.Unlock()
thisHook := &eventHook{
callback: callback,
}
em.app.applicationEventHooks[eventID] = append(em.app.applicationEventHooks[eventID], thisHook)
return func() {
em.app.applicationEventHooksLock.Lock()
em.app.applicationEventHooks[eventID] = slices.DeleteFunc(em.app.applicationEventHooks[eventID], func(h *eventHook) bool {
return h == thisHook
})
em.app.applicationEventHooksLock.Unlock()
}
}
// Dispatch dispatches an event to listeners (internal use)
func (em *EventManager) dispatch(event *CustomEvent) {
// Snapshot listeners under Lock
em.app.wailsEventListenerLock.Lock()
listeners := slices.Clone(em.app.wailsEventListeners)
em.app.wailsEventListenerLock.Unlock()
for _, listener := range listeners {
if event.IsCancelled() {
return
}
listener.DispatchWailsEvent(event)
}
}
// HandleApplicationEvent handles application events (internal use)
func (em *EventManager) handleApplicationEvent(event *ApplicationEvent) {
defer handlePanic()
em.app.applicationEventListenersLock.RLock()
listeners, ok := em.app.applicationEventListeners[event.Id]
em.app.applicationEventListenersLock.RUnlock()
if !ok {
return
}
// Process Hooks
em.app.applicationEventHooksLock.RLock()
hooks, ok := em.app.applicationEventHooks[event.Id]
em.app.applicationEventHooksLock.RUnlock()
if ok {
for _, thisHook := range hooks {
thisHook.callback(event)
if event.IsCancelled() {
return
}
}
}
for _, listener := range listeners {
go func() {
if event.IsCancelled() {
return
}
defer handlePanic()
listener.callback(event)
}()
}
}

File diff suppressed because it is too large Load diff

27
v3/pkg/w32/actions.go Normal file
View file

@ -0,0 +1,27 @@
//go:build windows
package w32
func Undo(hwnd HWND) {
SendMessage(hwnd, WM_UNDO, 0, 0)
}
func Cut(hwnd HWND) {
SendMessage(hwnd, WM_CUT, 0, 0)
}
func Copy(hwnd HWND) {
SendMessage(hwnd, WM_COPY, 0, 0)
}
func Paste(hwnd HWND) {
SendMessage(hwnd, WM_PASTE, 0, 0)
}
func Delete(hwnd HWND) {
SendMessage(hwnd, WM_CLEAR, 0, 0)
}
func SelectAll(hwnd HWND) {
SendMessage(hwnd, WM_SELECTALL, 0, 0)
}

143
v3/pkg/w32/clipboard.go Normal file
View file

@ -0,0 +1,143 @@
//go:build windows
/*
* Based on code originally from https://github.com/atotto/clipboard. Copyright (c) 2013 Ato Araki. All rights reserved.
*/
package w32
import (
"runtime"
"syscall"
"time"
"unsafe"
)
const (
cfUnicodetext = 13
gmemMoveable = 0x0002
)
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
func waitOpenClipboard() error {
started := time.Now()
limit := started.Add(time.Second)
var r uintptr
var err error
for time.Now().Before(limit) {
r, _, err = procOpenClipboard.Call(0)
if r != 0 {
return nil
}
time.Sleep(time.Millisecond)
}
return err
}
func GetClipboardText() (string, error) {
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if formatAvailable, _, err := procIsClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
return "", err
}
err := waitOpenClipboard()
if err != nil {
return "", err
}
h, _, err := procGetClipboardData.Call(cfUnicodetext)
if h == 0 {
_, _, _ = procCloseClipboard.Call()
return "", err
}
l, _, err := kernelGlobalLock.Call(h)
if l == 0 {
_, _, _ = procCloseClipboard.Call()
return "", err
}
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
r, _, err := kernelGlobalUnlock.Call(h)
if r == 0 {
_, _, _ = procCloseClipboard.Call()
return "", err
}
closed, _, err := procCloseClipboard.Call()
if closed == 0 {
return "", err
}
return text, nil
}
func SetClipboardText(text string) error {
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := waitOpenClipboard()
if err != nil {
return err
}
r, _, err := procEmptyClipboard.Call(0)
if r == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
data, err := syscall.UTF16FromString(text)
if err != nil {
return err
}
// "If the hMem parameter identifies a memory object, the object must have
// been allocated using the function with the GMEM_MOVEABLE flag."
h, _, err := kernelGlobalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
if h == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
defer func() {
if h != 0 {
kernelGlobalFree.Call(h)
}
}()
l, _, err := kernelGlobalLock.Call(h)
if l == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
r, _, err = kernelLstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
if r == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
r, _, err = kernelGlobalUnlock.Call(h)
if r == 0 {
if err.(syscall.Errno) != 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
}
r, _, err = procSetClipboardData.Call(cfUnicodetext, h)
if r == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
h = 0 // suppress deferred cleanup
closed, _, err := procCloseClipboard.Call()
if closed == 0 {
return err
}
return nil
}

55
v3/pkg/w32/com.go Normal file
View file

@ -0,0 +1,55 @@
//go:build windows
package w32
import (
"golang.org/x/sys/windows"
"syscall"
"unsafe"
)
// ComProc stores a COM procedure.
type ComProc uintptr
// NewComProc creates a new COM proc from a Go function.
func NewComProc(fn interface{}) ComProc {
return ComProc(windows.NewCallback(fn))
}
type EventRegistrationToken struct {
value int64
}
// IUnknown
type IUnknown struct {
Vtbl *IUnknownVtbl
}
type IUnknownVtbl struct {
QueryInterface ComProc
AddRef ComProc
Release ComProc
}
func (i *IUnknownVtbl) CallRelease(this unsafe.Pointer) error {
_, _, err := i.Release.Call(
uintptr(this),
)
if err != windows.ERROR_SUCCESS {
return err
}
return nil
}
type IUnknownImpl interface {
QueryInterface(refiid, object uintptr) uintptr
AddRef() uintptr
Release() uintptr
}
// Call calls a COM procedure.
//
//go:uintptrescapes
func (p ComProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
return syscall.SyscallN(uintptr(p), a...)
}

112
v3/pkg/w32/comctl32.go Normal file
View file

@ -0,0 +1,112 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modcomctl32 = syscall.NewLazyDLL("comctl32.dll")
procInitCommonControlsEx = modcomctl32.NewProc("InitCommonControlsEx")
procImageList_Create = modcomctl32.NewProc("ImageList_Create")
procImageList_Destroy = modcomctl32.NewProc("ImageList_Destroy")
procImageList_GetImageCount = modcomctl32.NewProc("ImageList_GetImageCount")
procImageList_SetImageCount = modcomctl32.NewProc("ImageList_SetImageCount")
procImageList_Add = modcomctl32.NewProc("ImageList_Add")
procImageList_ReplaceIcon = modcomctl32.NewProc("ImageList_ReplaceIcon")
procImageList_Remove = modcomctl32.NewProc("ImageList_Remove")
procTrackMouseEvent = modcomctl32.NewProc("_TrackMouseEvent")
)
func InitCommonControlsEx(lpInitCtrls *INITCOMMONCONTROLSEX) bool {
ret, _, _ := procInitCommonControlsEx.Call(
uintptr(unsafe.Pointer(lpInitCtrls)))
return ret != 0
}
func ImageList_Create(cx, cy int, flags uint, cInitial, cGrow int) HIMAGELIST {
ret, _, _ := procImageList_Create.Call(
uintptr(cx),
uintptr(cy),
uintptr(flags),
uintptr(cInitial),
uintptr(cGrow))
if ret == 0 {
panic("Create image list failed")
}
return HIMAGELIST(ret)
}
func ImageList_Destroy(himl HIMAGELIST) bool {
ret, _, _ := procImageList_Destroy.Call(
uintptr(himl))
return ret != 0
}
func ImageList_GetImageCount(himl HIMAGELIST) int {
ret, _, _ := procImageList_GetImageCount.Call(
uintptr(himl))
return int(ret)
}
func ImageList_SetImageCount(himl HIMAGELIST, uNewCount uint) bool {
ret, _, _ := procImageList_SetImageCount.Call(
uintptr(himl),
uintptr(uNewCount))
return ret != 0
}
func ImageList_Add(himl HIMAGELIST, hbmImage, hbmMask HBITMAP) int {
ret, _, _ := procImageList_Add.Call(
uintptr(himl),
uintptr(hbmImage),
uintptr(hbmMask))
return int(ret)
}
func ImageList_ReplaceIcon(himl HIMAGELIST, i int, hicon HICON) int {
ret, _, _ := procImageList_ReplaceIcon.Call(
uintptr(himl),
uintptr(i),
uintptr(hicon))
return int(ret)
}
func ImageList_AddIcon(himl HIMAGELIST, hicon HICON) int {
return ImageList_ReplaceIcon(himl, -1, hicon)
}
func ImageList_Remove(himl HIMAGELIST, i int) bool {
ret, _, _ := procImageList_Remove.Call(
uintptr(himl),
uintptr(i))
return ret != 0
}
func ImageList_RemoveAll(himl HIMAGELIST) bool {
return ImageList_Remove(himl, -1)
}
func TrackMouseEvent(tme *TRACKMOUSEEVENT) bool {
ret, _, _ := procTrackMouseEvent.Call(
uintptr(unsafe.Pointer(tme)))
return ret != 0
}

40
v3/pkg/w32/comdlg32.go Normal file
View file

@ -0,0 +1,40 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modcomdlg32 = syscall.NewLazyDLL("comdlg32.dll")
procGetSaveFileName = modcomdlg32.NewProc("GetSaveFileNameW")
procGetOpenFileName = modcomdlg32.NewProc("GetOpenFileNameW")
procCommDlgExtendedError = modcomdlg32.NewProc("CommDlgExtendedError")
)
func GetOpenFileName(ofn *OPENFILENAME) bool {
ret, _, _ := procGetOpenFileName.Call(
uintptr(unsafe.Pointer(ofn)))
return ret != 0
}
func GetSaveFileName(ofn *OPENFILENAME) bool {
ret, _, _ := procGetSaveFileName.Call(
uintptr(unsafe.Pointer(ofn)))
return ret != 0
}
func CommDlgExtendedError() uint {
ret, _, _ := procCommDlgExtendedError.Call()
return uint(ret)
}

3731
v3/pkg/w32/constants.go Normal file

File diff suppressed because it is too large Load diff

102
v3/pkg/w32/consts.go Normal file
View file

@ -0,0 +1,102 @@
//go:build windows
package w32
import (
"fmt"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"strconv"
"syscall"
"unsafe"
)
var (
modwingdi = syscall.NewLazyDLL("gdi32.dll")
procCreateSolidBrush = modwingdi.NewProc("CreateSolidBrush")
)
var (
kernel32 = syscall.NewLazyDLL("kernel32")
kernelGlobalAlloc = kernel32.NewProc("GlobalAlloc")
kernelGlobalFree = kernel32.NewProc("GlobalFree")
kernelGlobalLock = kernel32.NewProc("GlobalLock")
kernelGlobalUnlock = kernel32.NewProc("GlobalUnlock")
kernelLstrcpy = kernel32.NewProc("lstrcpyW")
)
var (
modBranding = syscall.NewLazyDLL("winbrand.dll")
brandingFormatString = modBranding.NewProc("BrandingFormatString")
)
var windowsVersion, _ = GetWindowsVersionInfo()
func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
return windowsVersion.Major >= major &&
windowsVersion.Minor >= minor &&
windowsVersion.Build >= buildNumber
}
type WindowsVersionInfo struct {
Major int
Minor int
Build int
DisplayVersion string
}
func (w *WindowsVersionInfo) String() string {
return fmt.Sprintf("%d.%d.%d (%s)", w.Major, w.Minor, w.Build, w.DisplayVersion)
}
func (w *WindowsVersionInfo) IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
return w.Major >= major && w.Minor >= minor && w.Build >= buildNumber
}
func GetBranding() string {
windowsLong := MustStringToUTF16Ptr("%WINDOWS_LONG%\x00")
ret, _, _ := brandingFormatString.Call(
uintptr(unsafe.Pointer(windowsLong)),
)
return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ret)))
}
func GetWindowsVersionInfo() (*WindowsVersionInfo, error) {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil {
return nil, err
}
return &WindowsVersionInfo{
Major: regDWORDKeyAsInt(key, "CurrentMajorVersionNumber"),
Minor: regDWORDKeyAsInt(key, "CurrentMinorVersionNumber"),
Build: regStringKeyAsInt(key, "CurrentBuildNumber"),
DisplayVersion: regKeyAsString(key, "DisplayVersion"),
}, nil
}
func regDWORDKeyAsInt(key registry.Key, name string) int {
result, _, err := key.GetIntegerValue(name)
if err != nil {
return -1
}
return int(result)
}
func regStringKeyAsInt(key registry.Key, name string) int {
resultStr, _, err := key.GetStringValue(name)
if err != nil {
return -1
}
result, err := strconv.Atoi(resultStr)
if err != nil {
return -1
}
return result
}
func regKeyAsString(key registry.Key, name string) string {
resultStr, _, err := key.GetStringValue(name)
if err != nil {
return ""
}
return resultStr
}

28
v3/pkg/w32/dialogs.go Normal file
View file

@ -0,0 +1,28 @@
//go:build windows
package w32
import (
"unsafe"
)
func MessageBoxWithIcon(hwnd HWND, text *uint16, caption *uint16, iconID int, flags uint32) (int32, error) {
params := MSGBOXPARAMS{
cbSize: uint32(unsafe.Sizeof(MSGBOXPARAMS{})),
hwndOwner: hwnd,
hInstance: GetApplicationHandle(),
lpszText: text,
lpszCaption: caption,
dwStyle: flags,
lpszIcon: (*uint16)(unsafe.Pointer(uintptr(iconID))),
}
r, _, err := procMessageBoxIndirect.Call(
uintptr(unsafe.Pointer(&params)),
)
if r == 0 {
return 0, err
}
return int32(r), nil
}

46
v3/pkg/w32/dwmapi.go Normal file
View file

@ -0,0 +1,46 @@
//go:build windows
package w32
import (
"syscall"
"unsafe"
)
var (
moddwmapi = syscall.NewLazyDLL("dwmapi.dll")
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute")
procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea")
)
func DwmSetWindowAttribute(hwnd HWND, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) HRESULT {
ret, _, _ := procDwmSetWindowAttribute.Call(
hwnd,
uintptr(dwAttribute),
uintptr(pvAttribute),
cbAttribute)
return HRESULT(ret)
}
func DwmGetWindowAttribute(hwnd HWND, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) HRESULT {
ret, _, _ := procDwmGetWindowAttribute.Call(
hwnd,
uintptr(dwAttribute),
uintptr(pvAttribute),
cbAttribute)
return HRESULT(ret)
}
func dwmExtendFrameIntoClientArea(hwnd uintptr, margins *MARGINS) error {
ret, _, _ := procDwmExtendFrameIntoClientArea.Call(
hwnd,
uintptr(unsafe.Pointer(margins)))
if ret != 0 {
return syscall.GetLastError()
}
return nil
}

581
v3/pkg/w32/gdi32.go Normal file
View file

@ -0,0 +1,581 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modgdi32 = syscall.NewLazyDLL("gdi32.dll")
procGetDeviceCaps = modgdi32.NewProc("GetDeviceCaps")
procDeleteObject = modgdi32.NewProc("DeleteObject")
procCreateFontIndirect = modgdi32.NewProc("CreateFontIndirectW")
procAbortDoc = modgdi32.NewProc("AbortDoc")
procBitBlt = modgdi32.NewProc("BitBlt")
procPatBlt = modgdi32.NewProc("PatBlt")
procCloseEnhMetaFile = modgdi32.NewProc("CloseEnhMetaFile")
procCopyEnhMetaFile = modgdi32.NewProc("CopyEnhMetaFileW")
procCreateBrushIndirect = modgdi32.NewProc("CreateBrushIndirect")
procCreateCompatibleDC = modgdi32.NewProc("CreateCompatibleDC")
procCreateDC = modgdi32.NewProc("CreateDCW")
procCreateDIBSection = modgdi32.NewProc("CreateDIBSection")
procCreateEnhMetaFile = modgdi32.NewProc("CreateEnhMetaFileW")
procCreateIC = modgdi32.NewProc("CreateICW")
procDeleteDC = modgdi32.NewProc("DeleteDC")
procDeleteEnhMetaFile = modgdi32.NewProc("DeleteEnhMetaFile")
procEllipse = modgdi32.NewProc("Ellipse")
procEndDoc = modgdi32.NewProc("EndDoc")
procEndPage = modgdi32.NewProc("EndPage")
procExtCreatePen = modgdi32.NewProc("ExtCreatePen")
procGetEnhMetaFile = modgdi32.NewProc("GetEnhMetaFileW")
procGetEnhMetaFileHeader = modgdi32.NewProc("GetEnhMetaFileHeader")
procGetObject = modgdi32.NewProc("GetObjectW")
procGetStockObject = modgdi32.NewProc("GetStockObject")
procGetTextExtentExPoint = modgdi32.NewProc("GetTextExtentExPointW")
procGetTextExtentPoint32 = modgdi32.NewProc("GetTextExtentPoint32W")
procGetTextMetrics = modgdi32.NewProc("GetTextMetricsW")
procLineTo = modgdi32.NewProc("LineTo")
procMoveToEx = modgdi32.NewProc("MoveToEx")
procPlayEnhMetaFile = modgdi32.NewProc("PlayEnhMetaFile")
procRectangle = modgdi32.NewProc("Rectangle")
procResetDC = modgdi32.NewProc("ResetDCW")
procSelectObject = modgdi32.NewProc("SelectObject")
procSetBkMode = modgdi32.NewProc("SetBkMode")
procSetBrushOrgEx = modgdi32.NewProc("SetBrushOrgEx")
procSetStretchBltMode = modgdi32.NewProc("SetStretchBltMode")
procSetTextColor = modgdi32.NewProc("SetTextColor")
procSetBkColor = modgdi32.NewProc("SetBkColor")
procStartDoc = modgdi32.NewProc("StartDocW")
procStartPage = modgdi32.NewProc("StartPage")
procStretchBlt = modgdi32.NewProc("StretchBlt")
procSetDIBitsToDevice = modgdi32.NewProc("SetDIBitsToDevice")
procChoosePixelFormat = modgdi32.NewProc("ChoosePixelFormat")
procDescribePixelFormat = modgdi32.NewProc("DescribePixelFormat")
procGetEnhMetaFilePixelFormat = modgdi32.NewProc("GetEnhMetaFilePixelFormat")
procGetPixelFormat = modgdi32.NewProc("GetPixelFormat")
procSetPixelFormat = modgdi32.NewProc("SetPixelFormat")
procSwapBuffers = modgdi32.NewProc("SwapBuffers")
procSaveDC = modgdi32.NewProc("SaveDC")
procRestoreDC = modgdi32.NewProc("RestoreDC")
procSelectClipRgn = modgdi32.NewProc("SelectClipRgn")
procExcludeClipRect = modgdi32.NewProc("ExcludeClipRect")
procExtTextOut = modgdi32.NewProc("ExtTextOutW")
)
func GetDeviceCaps(hdc HDC, index int) int {
ret, _, _ := procGetDeviceCaps.Call(
uintptr(hdc),
uintptr(index))
return int(ret)
}
func DeleteObject(hObject HGDIOBJ) bool {
ret, _, _ := procDeleteObject.Call(
uintptr(hObject))
return ret != 0
}
func CreateFontIndirect(logFont *LOGFONT) HFONT {
ret, _, _ := procCreateFontIndirect.Call(
uintptr(unsafe.Pointer(logFont)))
return HFONT(ret)
}
func AbortDoc(hdc HDC) int {
ret, _, _ := procAbortDoc.Call(
uintptr(hdc))
return int(ret)
}
func BitBlt(hdcDest HDC, nXDest, nYDest, nWidth, nHeight int, hdcSrc HDC, nXSrc, nYSrc int, dwRop uint) {
ret, _, _ := procBitBlt.Call(
uintptr(hdcDest),
uintptr(nXDest),
uintptr(nYDest),
uintptr(nWidth),
uintptr(nHeight),
uintptr(hdcSrc),
uintptr(nXSrc),
uintptr(nYSrc),
uintptr(dwRop))
if ret == 0 {
panic("BitBlt failed")
}
}
func PatBlt(hdc HDC, nXLeft, nYLeft, nWidth, nHeight int, dwRop uint) {
ret, _, _ := procPatBlt.Call(
uintptr(hdc),
uintptr(nXLeft),
uintptr(nYLeft),
uintptr(nWidth),
uintptr(nHeight),
uintptr(dwRop))
if ret == 0 {
panic("PatBlt failed")
}
}
func CloseEnhMetaFile(hdc HDC) HENHMETAFILE {
ret, _, _ := procCloseEnhMetaFile.Call(
uintptr(hdc))
return HENHMETAFILE(ret)
}
func CopyEnhMetaFile(hemfSrc HENHMETAFILE, lpszFile *uint16) HENHMETAFILE {
ret, _, _ := procCopyEnhMetaFile.Call(
uintptr(hemfSrc),
uintptr(unsafe.Pointer(lpszFile)))
return HENHMETAFILE(ret)
}
func CreateBrushIndirect(lplb *LOGBRUSH) HBRUSH {
ret, _, _ := procCreateBrushIndirect.Call(
uintptr(unsafe.Pointer(lplb)))
return HBRUSH(ret)
}
func CreateCompatibleDC(hdc HDC) HDC {
ret, _, _ := procCreateCompatibleDC.Call(
uintptr(hdc))
if ret == 0 {
panic("Create compatible DC failed")
}
return HDC(ret)
}
func CreateDC(lpszDriver, lpszDevice, lpszOutput *uint16, lpInitData *DEVMODE) HDC {
ret, _, _ := procCreateDC.Call(
uintptr(unsafe.Pointer(lpszDriver)),
uintptr(unsafe.Pointer(lpszDevice)),
uintptr(unsafe.Pointer(lpszOutput)),
uintptr(unsafe.Pointer(lpInitData)))
return HDC(ret)
}
func CreateDIBSection(hdc HDC, pbmi *BITMAPINFO, iUsage uint, ppvBits *unsafe.Pointer, hSection HANDLE, dwOffset uint) HBITMAP {
ret, _, _ := procCreateDIBSection.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(pbmi)),
uintptr(iUsage),
uintptr(unsafe.Pointer(ppvBits)),
uintptr(hSection),
uintptr(dwOffset))
return HBITMAP(ret)
}
func CreateEnhMetaFile(hdcRef HDC, lpFilename *uint16, lpRect *RECT, lpDescription *uint16) HDC {
ret, _, _ := procCreateEnhMetaFile.Call(
uintptr(hdcRef),
uintptr(unsafe.Pointer(lpFilename)),
uintptr(unsafe.Pointer(lpRect)),
uintptr(unsafe.Pointer(lpDescription)))
return HDC(ret)
}
func CreateIC(lpszDriver, lpszDevice, lpszOutput *uint16, lpdvmInit *DEVMODE) HDC {
ret, _, _ := procCreateIC.Call(
uintptr(unsafe.Pointer(lpszDriver)),
uintptr(unsafe.Pointer(lpszDevice)),
uintptr(unsafe.Pointer(lpszOutput)),
uintptr(unsafe.Pointer(lpdvmInit)))
return HDC(ret)
}
func DeleteDC(hdc HDC) bool {
ret, _, _ := procDeleteDC.Call(
uintptr(hdc))
return ret != 0
}
func DeleteEnhMetaFile(hemf HENHMETAFILE) bool {
ret, _, _ := procDeleteEnhMetaFile.Call(
uintptr(hemf))
return ret != 0
}
func Ellipse(hdc HDC, nLeftRect, nTopRect, nRightRect, nBottomRect int32) bool {
ret, _, _ := procEllipse.Call(
uintptr(hdc),
uintptr(nLeftRect),
uintptr(nTopRect),
uintptr(nRightRect),
uintptr(nBottomRect))
return ret != 0
}
func EndDoc(hdc HDC) int {
ret, _, _ := procEndDoc.Call(
uintptr(hdc))
return int(ret)
}
func EndPage(hdc HDC) int {
ret, _, _ := procEndPage.Call(
uintptr(hdc))
return int(ret)
}
func ExtCreatePen(dwPenStyle, dwWidth uint, lplb *LOGBRUSH, dwStyleCount uint, lpStyle *uint) HPEN {
ret, _, _ := procExtCreatePen.Call(
uintptr(dwPenStyle),
uintptr(dwWidth),
uintptr(unsafe.Pointer(lplb)),
uintptr(dwStyleCount),
uintptr(unsafe.Pointer(lpStyle)))
return HPEN(ret)
}
func GetEnhMetaFile(lpszMetaFile *uint16) HENHMETAFILE {
ret, _, _ := procGetEnhMetaFile.Call(
uintptr(unsafe.Pointer(lpszMetaFile)))
return HENHMETAFILE(ret)
}
func GetEnhMetaFileHeader(hemf HENHMETAFILE, cbBuffer uint, lpemh *ENHMETAHEADER) uint {
ret, _, _ := procGetEnhMetaFileHeader.Call(
uintptr(hemf),
uintptr(cbBuffer),
uintptr(unsafe.Pointer(lpemh)))
return uint(ret)
}
func GetObject(hgdiobj HGDIOBJ, cbBuffer uintptr, lpvObject unsafe.Pointer) int {
ret, _, _ := procGetObject.Call(
uintptr(hgdiobj),
uintptr(cbBuffer),
uintptr(lpvObject))
return int(ret)
}
func GetStockObject(fnObject int) HGDIOBJ {
ret, _, _ := procGetDeviceCaps.Call(
uintptr(fnObject))
return HGDIOBJ(ret)
}
func GetTextExtentExPoint(hdc HDC, lpszStr *uint16, cchString, nMaxExtent int, lpnFit, alpDx *int, lpSize *SIZE) bool {
ret, _, _ := procGetTextExtentExPoint.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lpszStr)),
uintptr(cchString),
uintptr(nMaxExtent),
uintptr(unsafe.Pointer(lpnFit)),
uintptr(unsafe.Pointer(alpDx)),
uintptr(unsafe.Pointer(lpSize)))
return ret != 0
}
func GetTextExtentPoint32(hdc HDC, lpString *uint16, c int, lpSize *SIZE) bool {
ret, _, _ := procGetTextExtentPoint32.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lpString)),
uintptr(c),
uintptr(unsafe.Pointer(lpSize)))
return ret != 0
}
func GetTextMetrics(hdc HDC, lptm *TEXTMETRIC) bool {
ret, _, _ := procGetTextMetrics.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lptm)))
return ret != 0
}
func LineTo(hdc HDC, nXEnd, nYEnd int32) bool {
ret, _, _ := procLineTo.Call(
uintptr(hdc),
uintptr(nXEnd),
uintptr(nYEnd))
return ret != 0
}
func MoveToEx(hdc HDC, x, y int, lpPoint *POINT) bool {
ret, _, _ := procMoveToEx.Call(
uintptr(hdc),
uintptr(x),
uintptr(y),
uintptr(unsafe.Pointer(lpPoint)))
return ret != 0
}
func PlayEnhMetaFile(hdc HDC, hemf HENHMETAFILE, lpRect *RECT) bool {
ret, _, _ := procPlayEnhMetaFile.Call(
uintptr(hdc),
uintptr(hemf),
uintptr(unsafe.Pointer(lpRect)))
return ret != 0
}
func Rectangle(hdc HDC, nLeftRect, nTopRect, nRightRect, nBottomRect int32) bool {
ret, _, _ := procRectangle.Call(
uintptr(hdc),
uintptr(nLeftRect),
uintptr(nTopRect),
uintptr(nRightRect),
uintptr(nBottomRect))
return ret != 0
}
func ResetDC(hdc HDC, lpInitData *DEVMODE) HDC {
ret, _, _ := procResetDC.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lpInitData)))
return HDC(ret)
}
func SelectObject(hdc HDC, hgdiobj HGDIOBJ) HGDIOBJ {
ret, _, _ := procSelectObject.Call(
uintptr(hdc),
uintptr(hgdiobj))
if ret == 0 {
panic("SelectObject failed")
}
return HGDIOBJ(ret)
}
func SetBkMode(hdc HDC, iBkMode int) int {
ret, _, _ := procSetBkMode.Call(
uintptr(hdc),
uintptr(iBkMode))
if ret == 0 {
panic("SetBkMode failed")
}
return int(ret)
}
func SetBrushOrgEx(hdc HDC, nXOrg, nYOrg int, lppt *POINT) bool {
ret, _, _ := procSetBrushOrgEx.Call(
uintptr(hdc),
uintptr(nXOrg),
uintptr(nYOrg),
uintptr(unsafe.Pointer(lppt)))
return ret != 0
}
func SetStretchBltMode(hdc HDC, iStretchMode int) int {
ret, _, _ := procSetStretchBltMode.Call(
uintptr(hdc),
uintptr(iStretchMode))
return int(ret)
}
func SetTextColor(hdc HDC, crColor COLORREF) COLORREF {
ret, _, _ := procSetTextColor.Call(
uintptr(hdc),
uintptr(crColor))
if ret == CLR_INVALID {
panic("SetTextColor failed")
}
return COLORREF(ret)
}
func SetBkColor(hdc HDC, crColor COLORREF) COLORREF {
ret, _, _ := procSetBkColor.Call(
uintptr(hdc),
uintptr(crColor))
if ret == CLR_INVALID {
panic("SetBkColor failed")
}
return COLORREF(ret)
}
func StartDoc(hdc HDC, lpdi *DOCINFO) int {
ret, _, _ := procStartDoc.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lpdi)))
return int(ret)
}
func StartPage(hdc HDC) int {
ret, _, _ := procStartPage.Call(
uintptr(hdc))
return int(ret)
}
func StretchBlt(hdcDest HDC, nXOriginDest, nYOriginDest, nWidthDest, nHeightDest int, hdcSrc HDC, nXOriginSrc, nYOriginSrc, nWidthSrc, nHeightSrc int, dwRop uint) {
ret, _, _ := procStretchBlt.Call(
uintptr(hdcDest),
uintptr(nXOriginDest),
uintptr(nYOriginDest),
uintptr(nWidthDest),
uintptr(nHeightDest),
uintptr(hdcSrc),
uintptr(nXOriginSrc),
uintptr(nYOriginSrc),
uintptr(nWidthSrc),
uintptr(nHeightSrc),
uintptr(dwRop))
if ret == 0 {
panic("StretchBlt failed")
}
}
func SetDIBitsToDevice(hdc HDC, xDest, yDest, dwWidth, dwHeight, xSrc, ySrc int, uStartScan, cScanLines uint, lpvBits []byte, lpbmi *BITMAPINFO, fuColorUse uint) int {
ret, _, _ := procSetDIBitsToDevice.Call(
uintptr(hdc),
uintptr(xDest),
uintptr(yDest),
uintptr(dwWidth),
uintptr(dwHeight),
uintptr(xSrc),
uintptr(ySrc),
uintptr(uStartScan),
uintptr(cScanLines),
uintptr(unsafe.Pointer(&lpvBits[0])),
uintptr(unsafe.Pointer(lpbmi)),
uintptr(fuColorUse))
return int(ret)
}
func ChoosePixelFormat(hdc HDC, pfd *PIXELFORMATDESCRIPTOR) int {
ret, _, _ := procChoosePixelFormat.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(pfd)),
)
return int(ret)
}
func DescribePixelFormat(hdc HDC, iPixelFormat int, nBytes uint, pfd *PIXELFORMATDESCRIPTOR) int {
ret, _, _ := procDescribePixelFormat.Call(
uintptr(hdc),
uintptr(iPixelFormat),
uintptr(nBytes),
uintptr(unsafe.Pointer(pfd)),
)
return int(ret)
}
func GetEnhMetaFilePixelFormat(hemf HENHMETAFILE, cbBuffer uint32, pfd *PIXELFORMATDESCRIPTOR) uint {
ret, _, _ := procGetEnhMetaFilePixelFormat.Call(
uintptr(hemf),
uintptr(cbBuffer),
uintptr(unsafe.Pointer(pfd)),
)
return uint(ret)
}
func GetPixelFormat(hdc HDC) int {
ret, _, _ := procGetPixelFormat.Call(
uintptr(hdc),
)
return int(ret)
}
func SetPixelFormat(hdc HDC, iPixelFormat int, pfd *PIXELFORMATDESCRIPTOR) bool {
ret, _, _ := procSetPixelFormat.Call(
uintptr(hdc),
uintptr(iPixelFormat),
uintptr(unsafe.Pointer(pfd)),
)
return ret == TRUE
}
func SwapBuffers(hdc HDC) bool {
ret, _, _ := procSwapBuffers.Call(uintptr(hdc))
return ret == TRUE
}
func SaveDC(hdc HDC) int {
ret, _, _ := procSaveDC.Call(uintptr(hdc))
return int(ret)
}
func RestoreDC(hdc HDC, nSavedDC int) bool {
ret, _, _ := procRestoreDC.Call(
uintptr(hdc),
uintptr(nSavedDC))
return ret != 0
}
func SelectClipRgn(hdc HDC, hrgn HRGN) int {
ret, _, _ := procSelectClipRgn.Call(
uintptr(hdc),
uintptr(hrgn))
return int(ret)
}
func ExcludeClipRect(hdc HDC, left, top, right, bottom int32) int {
ret, _, _ := procExcludeClipRect.Call(
uintptr(hdc),
uintptr(left),
uintptr(top),
uintptr(right),
uintptr(bottom))
return int(ret)
}
func ExtTextOut(hdc HDC, x, y int32, fuOptions uint32, lprc *RECT, lpString *uint16, cbCount uint32, lpDx *int) bool {
var rectPtr uintptr
if lprc != nil {
rectPtr = uintptr(unsafe.Pointer(lprc))
}
var dxPtr uintptr
if lpDx != nil {
dxPtr = uintptr(unsafe.Pointer(lpDx))
}
ret, _, _ := procExtTextOut.Call(
uintptr(hdc),
uintptr(x),
uintptr(y),
uintptr(fuOptions),
rectPtr,
uintptr(unsafe.Pointer(lpString)),
uintptr(cbCount),
dxPtr)
return ret != 0
}

177
v3/pkg/w32/gdiplus.go Normal file
View file

@ -0,0 +1,177 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"errors"
"fmt"
"syscall"
"unsafe"
)
const (
Ok = 0
GenericError = 1
InvalidParameter = 2
OutOfMemory = 3
ObjectBusy = 4
InsufficientBuffer = 5
NotImplemented = 6
Win32Error = 7
WrongState = 8
Aborted = 9
FileNotFound = 10
ValueOverflow = 11
AccessDenied = 12
UnknownImageFormat = 13
FontFamilyNotFound = 14
FontStyleNotFound = 15
NotTrueTypeFont = 16
UnsupportedGdiplusVersion = 17
GdiplusNotInitialized = 18
PropertyNotFound = 19
PropertyNotSupported = 20
ProfileNotFound = 21
)
func GetGpStatus(s int32) string {
switch s {
case Ok:
return "Ok"
case GenericError:
return "GenericError"
case InvalidParameter:
return "InvalidParameter"
case OutOfMemory:
return "OutOfMemory"
case ObjectBusy:
return "ObjectBusy"
case InsufficientBuffer:
return "InsufficientBuffer"
case NotImplemented:
return "NotImplemented"
case Win32Error:
return "Win32Error"
case WrongState:
return "WrongState"
case Aborted:
return "Aborted"
case FileNotFound:
return "FileNotFound"
case ValueOverflow:
return "ValueOverflow"
case AccessDenied:
return "AccessDenied"
case UnknownImageFormat:
return "UnknownImageFormat"
case FontFamilyNotFound:
return "FontFamilyNotFound"
case FontStyleNotFound:
return "FontStyleNotFound"
case NotTrueTypeFont:
return "NotTrueTypeFont"
case UnsupportedGdiplusVersion:
return "UnsupportedGdiplusVersion"
case GdiplusNotInitialized:
return "GdiplusNotInitialized"
case PropertyNotFound:
return "PropertyNotFound"
case PropertyNotSupported:
return "PropertyNotSupported"
case ProfileNotFound:
return "ProfileNotFound"
}
return "Unknown Status Value"
}
var (
token uintptr
modgdiplus = syscall.NewLazyDLL("gdiplus.dll")
procGdipCreateBitmapFromFile = modgdiplus.NewProc("GdipCreateBitmapFromFile")
procGdipCreateBitmapFromHBITMAP = modgdiplus.NewProc("GdipCreateBitmapFromHBITMAP")
procGdipCreateHBITMAPFromBitmap = modgdiplus.NewProc("GdipCreateHBITMAPFromBitmap")
procGdipCreateBitmapFromResource = modgdiplus.NewProc("GdipCreateBitmapFromResource")
procGdipCreateBitmapFromStream = modgdiplus.NewProc("GdipCreateBitmapFromStream")
procGdipDisposeImage = modgdiplus.NewProc("GdipDisposeImage")
procGdiplusShutdown = modgdiplus.NewProc("GdiplusShutdown")
procGdiplusStartup = modgdiplus.NewProc("GdiplusStartup")
)
func GdipCreateBitmapFromFile(filename string) (*uintptr, error) {
var bitmap *uintptr
ret, _, _ := procGdipCreateBitmapFromFile.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(filename))),
uintptr(unsafe.Pointer(&bitmap)))
if ret != Ok {
return nil, errors.New(fmt.Sprintf("GdipCreateBitmapFromFile failed with status '%s' for file '%s'", GetGpStatus(int32(ret)), filename))
}
return bitmap, nil
}
func GdipCreateBitmapFromResource(instance HINSTANCE, resId *uint16) (*uintptr, error) {
var bitmap *uintptr
ret, _, _ := procGdipCreateBitmapFromResource.Call(
uintptr(instance),
uintptr(unsafe.Pointer(resId)),
uintptr(unsafe.Pointer(&bitmap)))
if ret != Ok {
return nil, errors.New(fmt.Sprintf("GdiCreateBitmapFromResource failed with status '%s'", GetGpStatus(int32(ret))))
}
return bitmap, nil
}
func GdipCreateBitmapFromStream(stream *IStream) (*uintptr, error) {
var bitmap *uintptr
ret, _, _ := procGdipCreateBitmapFromStream.Call(
uintptr(unsafe.Pointer(stream)),
uintptr(unsafe.Pointer(&bitmap)))
if ret != Ok {
return nil, errors.New(fmt.Sprintf("GdipCreateBitmapFromStream failed with status '%s'", GetGpStatus(int32(ret))))
}
return bitmap, nil
}
func GdipCreateHBITMAPFromBitmap(bitmap *uintptr, background uint32) (HBITMAP, error) {
var hbitmap HBITMAP
ret, _, _ := procGdipCreateHBITMAPFromBitmap.Call(
uintptr(unsafe.Pointer(bitmap)),
uintptr(unsafe.Pointer(&hbitmap)),
uintptr(background))
if ret != Ok {
return 0, errors.New(fmt.Sprintf("GdipCreateHBITMAPFromBitmap failed with status '%s'", GetGpStatus(int32(ret))))
}
return hbitmap, nil
}
func GdipDisposeImage(image *uintptr) {
procGdipDisposeImage.Call(uintptr(unsafe.Pointer(image)))
}
func GdiplusShutdown() {
procGdiplusShutdown.Call(token)
}
func GdiplusStartup(input *GdiplusStartupInput, output *GdiplusStartupOutput) {
ret, _, _ := procGdiplusStartup.Call(
uintptr(unsafe.Pointer(&token)),
uintptr(unsafe.Pointer(input)),
uintptr(unsafe.Pointer(output)))
if ret != Ok {
panic("GdiplusStartup failed with status " + GetGpStatus(int32(ret)))
}
}

225
v3/pkg/w32/guid.go Normal file
View file

@ -0,0 +1,225 @@
//go:build windows
package w32
// This code has been adapted from: https://github.com/go-ole/go-ole
/*
The MIT License (MIT)
Copyright © 2013-2017 Yasuhiro Matsumoto, <mattn.jp@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the Software), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
const hextable = "0123456789ABCDEF"
const emptyGUID = "{00000000-0000-0000-0000-000000000000}"
// GUID is Windows API specific GUID type.
//
// This exists to match Windows GUID type for direct passing for COM.
// Format is in xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx.
type GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
// NewGUID converts the given string into a globally unique identifier that is
// compliant with the Windows API.
//
// The supplied string may be in any of these formats:
//
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
//
// The conversion of the supplied string is not case-sensitive.
func NewGUID(guid string) *GUID {
d := []byte(guid)
var d1, d2, d3, d4a, d4b []byte
switch len(d) {
case 38:
if d[0] != '{' || d[37] != '}' {
return nil
}
d = d[1:37]
fallthrough
case 36:
if d[8] != '-' || d[13] != '-' || d[18] != '-' || d[23] != '-' {
return nil
}
d1 = d[0:8]
d2 = d[9:13]
d3 = d[14:18]
d4a = d[19:23]
d4b = d[24:36]
case 32:
d1 = d[0:8]
d2 = d[8:12]
d3 = d[12:16]
d4a = d[16:20]
d4b = d[20:32]
default:
return nil
}
var g GUID
var ok1, ok2, ok3, ok4 bool
g.Data1, ok1 = decodeHexUint32(d1)
g.Data2, ok2 = decodeHexUint16(d2)
g.Data3, ok3 = decodeHexUint16(d3)
g.Data4, ok4 = decodeHexByte64(d4a, d4b)
if ok1 && ok2 && ok3 && ok4 {
return &g
}
return nil
}
func decodeHexUint32(src []byte) (value uint32, ok bool) {
var b1, b2, b3, b4 byte
var ok1, ok2, ok3, ok4 bool
b1, ok1 = decodeHexByte(src[0], src[1])
b2, ok2 = decodeHexByte(src[2], src[3])
b3, ok3 = decodeHexByte(src[4], src[5])
b4, ok4 = decodeHexByte(src[6], src[7])
value = (uint32(b1) << 24) | (uint32(b2) << 16) | (uint32(b3) << 8) | uint32(b4)
ok = ok1 && ok2 && ok3 && ok4
return
}
func decodeHexUint16(src []byte) (value uint16, ok bool) {
var b1, b2 byte
var ok1, ok2 bool
b1, ok1 = decodeHexByte(src[0], src[1])
b2, ok2 = decodeHexByte(src[2], src[3])
value = (uint16(b1) << 8) | uint16(b2)
ok = ok1 && ok2
return
}
func decodeHexByte64(s1 []byte, s2 []byte) (value [8]byte, ok bool) {
var ok1, ok2, ok3, ok4, ok5, ok6, ok7, ok8 bool
value[0], ok1 = decodeHexByte(s1[0], s1[1])
value[1], ok2 = decodeHexByte(s1[2], s1[3])
value[2], ok3 = decodeHexByte(s2[0], s2[1])
value[3], ok4 = decodeHexByte(s2[2], s2[3])
value[4], ok5 = decodeHexByte(s2[4], s2[5])
value[5], ok6 = decodeHexByte(s2[6], s2[7])
value[6], ok7 = decodeHexByte(s2[8], s2[9])
value[7], ok8 = decodeHexByte(s2[10], s2[11])
ok = ok1 && ok2 && ok3 && ok4 && ok5 && ok6 && ok7 && ok8
return
}
func decodeHexByte(c1, c2 byte) (value byte, ok bool) {
var n1, n2 byte
var ok1, ok2 bool
n1, ok1 = decodeHexChar(c1)
n2, ok2 = decodeHexChar(c2)
value = (n1 << 4) | n2
ok = ok1 && ok2
return
}
func decodeHexChar(c byte) (byte, bool) {
switch {
case '0' <= c && c <= '9':
return c - '0', true
case 'a' <= c && c <= 'f':
return c - 'a' + 10, true
case 'A' <= c && c <= 'F':
return c - 'A' + 10, true
}
return 0, false
}
// String converts the GUID to string form. It will adhere to this pattern:
//
// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
//
// If the GUID is nil, the string representation of an empty GUID is returned:
//
// {00000000-0000-0000-0000-000000000000}
func (guid *GUID) String() string {
if guid == nil {
return emptyGUID
}
var c [38]byte
c[0] = '{'
putUint32Hex(c[1:9], guid.Data1)
c[9] = '-'
putUint16Hex(c[10:14], guid.Data2)
c[14] = '-'
putUint16Hex(c[15:19], guid.Data3)
c[19] = '-'
putByteHex(c[20:24], guid.Data4[0:2])
c[24] = '-'
putByteHex(c[25:37], guid.Data4[2:8])
c[37] = '}'
return string(c[:])
}
func putUint32Hex(b []byte, v uint32) {
b[0] = hextable[byte(v>>24)>>4]
b[1] = hextable[byte(v>>24)&0x0f]
b[2] = hextable[byte(v>>16)>>4]
b[3] = hextable[byte(v>>16)&0x0f]
b[4] = hextable[byte(v>>8)>>4]
b[5] = hextable[byte(v>>8)&0x0f]
b[6] = hextable[byte(v)>>4]
b[7] = hextable[byte(v)&0x0f]
}
func putUint16Hex(b []byte, v uint16) {
b[0] = hextable[byte(v>>8)>>4]
b[1] = hextable[byte(v>>8)&0x0f]
b[2] = hextable[byte(v)>>4]
b[3] = hextable[byte(v)&0x0f]
}
func putByteHex(dst, src []byte) {
for i := 0; i < len(src); i++ {
dst[i*2] = hextable[src[i]>>4]
dst[i*2+1] = hextable[src[i]&0x0f]
}
}
// IsEqualGUID compares two GUID.
//
// Not constant time comparison.
func IsEqualGUID(guid1 *GUID, guid2 *GUID) bool {
return guid1.Data1 == guid2.Data1 &&
guid1.Data2 == guid2.Data2 &&
guid1.Data3 == guid2.Data3 &&
guid1.Data4[0] == guid2.Data4[0] &&
guid1.Data4[1] == guid2.Data4[1] &&
guid1.Data4[2] == guid2.Data4[2] &&
guid1.Data4[3] == guid2.Data4[3] &&
guid1.Data4[4] == guid2.Data4[4] &&
guid1.Data4[5] == guid2.Data4[5] &&
guid1.Data4[6] == guid2.Data4[6] &&
guid1.Data4[7] == guid2.Data4[7]
}

255
v3/pkg/w32/icon.go Normal file
View file

@ -0,0 +1,255 @@
//go:build windows
package w32
import (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"os"
"syscall"
"unsafe"
)
func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, isIcon bool, version uint32, cxDesired int, cyDesired int, flags uint) (uintptr, error) {
icon := 0
if isIcon {
icon = 1
}
r, _, err := procCreateIconFromResourceEx.Call(
presbits,
uintptr(dwResSize),
uintptr(icon),
uintptr(version),
uintptr(cxDesired),
uintptr(cyDesired),
uintptr(flags),
)
if r == 0 {
return 0, err
}
return r, nil
}
func isPNG(fileData []byte) bool {
if len(fileData) < 4 {
return false
}
return string(fileData[:4]) == "\x89PNG"
}
func isICO(fileData []byte) bool {
if len(fileData) < 4 {
return false
}
return string(fileData[:4]) == "\x00\x00\x01\x00"
}
// CreateSmallHIconFromImage creates a HICON from a PNG or ICO file
func CreateSmallHIconFromImage(fileData []byte) (HICON, error) {
if len(fileData) < 8 {
return 0, fmt.Errorf("invalid file format")
}
if !isPNG(fileData) && !isICO(fileData) {
return 0, fmt.Errorf("unsupported file format")
}
iconWidth := GetSystemMetrics(SM_CXSMICON)
iconHeight := GetSystemMetrics(SM_CYSMICON)
icon, err := CreateIconFromResourceEx(
uintptr(unsafe.Pointer(&fileData[0])),
uint32(len(fileData)),
true,
0x00030000,
iconWidth,
iconHeight,
LR_DEFAULTSIZE)
return HICON(icon), err
}
// CreateLargeHIconFromImage creates a HICON from a PNG or ICO file
func CreateLargeHIconFromImage(fileData []byte) (HICON, error) {
if len(fileData) < 8 {
return 0, fmt.Errorf("invalid file format")
}
if !isPNG(fileData) && !isICO(fileData) {
return 0, fmt.Errorf("unsupported file format")
}
iconWidth := GetSystemMetrics(SM_CXICON)
iconHeight := GetSystemMetrics(SM_CXICON)
icon, err := CreateIconFromResourceEx(
uintptr(unsafe.Pointer(&fileData[0])),
uint32(len(fileData)),
true,
0x00030000,
iconWidth,
iconHeight,
LR_DEFAULTSIZE)
return HICON(icon), err
}
type ICONINFO struct {
FIcon int32
XHotspot int32
YHotspot int32
HbmMask syscall.Handle
HbmColor syscall.Handle
}
func SaveHIconAsPNG(hIcon HICON, filePath string) error {
// Load necessary DLLs
user32 := syscall.NewLazyDLL("user32.dll")
gdi32 := syscall.NewLazyDLL("gdi32.dll")
// Get procedures
getIconInfo := user32.NewProc("GetIconInfo")
getObject := gdi32.NewProc("GetObjectW")
createCompatibleDC := gdi32.NewProc("CreateCompatibleDC")
selectObject := gdi32.NewProc("SelectObject")
getDIBits := gdi32.NewProc("GetDIBits")
deleteObject := gdi32.NewProc("DeleteObject")
deleteDC := gdi32.NewProc("DeleteDC")
// Get icon info
var iconInfo ICONINFO
ret, _, err := getIconInfo.Call(
uintptr(hIcon),
uintptr(unsafe.Pointer(&iconInfo)),
)
if ret == 0 {
return err
}
defer deleteObject.Call(uintptr(iconInfo.HbmMask))
defer deleteObject.Call(uintptr(iconInfo.HbmColor))
// Get bitmap info
var bmp BITMAP
ret, _, err = getObject.Call(
uintptr(iconInfo.HbmColor),
unsafe.Sizeof(bmp),
uintptr(unsafe.Pointer(&bmp)),
)
if ret == 0 {
return err
}
// Create DC
hdc, _, _ := createCompatibleDC.Call(0)
if hdc == 0 {
return syscall.EINVAL
}
defer deleteDC.Call(hdc)
// Select bitmap into DC
oldBitmap, _, _ := selectObject.Call(hdc, uintptr(iconInfo.HbmColor))
defer selectObject.Call(hdc, oldBitmap)
// Prepare bitmap info header
var bi BITMAPINFO
bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader))
bi.BmiHeader.BiWidth = bmp.BmWidth
bi.BmiHeader.BiHeight = bmp.BmHeight
bi.BmiHeader.BiPlanes = 1
bi.BmiHeader.BiBitCount = 32
bi.BmiHeader.BiCompression = BI_RGB
// Allocate memory for bitmap bits
width, height := int(bmp.BmWidth), int(bmp.BmHeight)
bufferSize := width * height * 4
bits := make([]byte, bufferSize)
// Get bitmap bits
ret, _, err = getDIBits.Call(
hdc,
uintptr(iconInfo.HbmColor),
0,
uintptr(bmp.BmHeight),
uintptr(unsafe.Pointer(&bits[0])),
uintptr(unsafe.Pointer(&bi)),
DIB_RGB_COLORS,
)
if ret == 0 {
return err
}
// Create Go image
img := image.NewRGBA(image.Rect(0, 0, width, height))
// Convert DIB to RGBA
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
// DIB is bottom-up, so we need to invert Y
dibIndex := ((height-1-y)*width + x) * 4
// BGRA to RGBA
b := bits[dibIndex]
g := bits[dibIndex+1]
r := bits[dibIndex+2]
a := bits[dibIndex+3]
// Set pixel in the image
img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a})
}
}
// Create output file
outFile, err := os.Create(filePath)
if err != nil {
return err
}
defer outFile.Close()
// Encode and save the image
return png.Encode(outFile, img)
}
func SetWindowIcon(hwnd HWND, icon HICON) {
SendMessage(hwnd, WM_SETICON, ICON_SMALL, uintptr(icon))
}
func pngToImage(data []byte) (*image.RGBA, error) {
img, err := png.Decode(bytes.NewReader(data))
if err != nil {
return nil, err
}
bounds := img.Bounds()
rgba := image.NewRGBA(bounds)
draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)
return rgba, nil
}
func SetMenuIcons(parentMenu HMENU, itemID int, unchecked []byte, checked []byte) error {
if unchecked == nil {
return fmt.Errorf("invalid unchecked bitmap")
}
var err error
var uncheckedIcon, checkedIcon HBITMAP
var uncheckedImage, checkedImage *image.RGBA
uncheckedImage, err = pngToImage(unchecked)
if err != nil {
return err
}
uncheckedIcon, err = CreateHBITMAPFromImage(uncheckedImage)
if err != nil {
return err
}
if checked != nil {
checkedImage, err = pngToImage(checked)
if err != nil {
return err
}
checkedIcon, err = CreateHBITMAPFromImage(checkedImage)
if err != nil {
return err
}
} else {
checkedIcon = uncheckedIcon
}
return SetMenuItemBitmaps(parentMenu, uint32(itemID), MF_BYCOMMAND, checkedIcon, uncheckedIcon)
}

168
v3/pkg/w32/idataobject.go Normal file
View file

@ -0,0 +1,168 @@
//go:build windows
package w32
import (
"golang.org/x/sys/windows"
"syscall"
"unsafe"
)
type IDataObjectVtbl struct {
IUnknownVtbl
GetData ComProc
GetDataHere ComProc
QueryGetData ComProc
GetCanonicalFormatEtc ComProc
SetData ComProc
EnumFormatEtc ComProc
DAdvise ComProc
}
type IDataObject struct {
Vtbl *IDataObjectVtbl
}
func (i *IDataObject) AddRef() uintptr {
refCounter, _, _ := i.Vtbl.AddRef.Call(uintptr(unsafe.Pointer(i)))
return refCounter
}
func (i *IDataObject) GetData(formatEtc *FORMATETC, medium *STGMEDIUM) error {
hr, _, err := i.Vtbl.GetData.Call(
uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(formatEtc)),
uintptr(unsafe.Pointer(medium)),
)
if windows.Handle(hr) != windows.S_OK {
return syscall.Errno(hr)
}
return err
}
func (i *IDataObject) GetDataHere(formatEtc *FORMATETC, medium *STGMEDIUM) error {
hr, _, err := i.Vtbl.GetDataHere.Call(
uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(formatEtc)),
uintptr(unsafe.Pointer(medium)),
)
if windows.Handle(hr) != windows.S_OK {
return syscall.Errno(hr)
}
return err
}
func (i *IDataObject) QueryGetData(formatEtc *FORMATETC) error {
hr, _, err := i.Vtbl.QueryGetData.Call(
uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(formatEtc)),
)
if windows.Handle(hr) != windows.S_OK {
return syscall.Errno(hr)
}
return err
}
func (i *IDataObject) GetCanonicalFormatEtc(inputFormatEtc *FORMATETC, outputFormatEtc *FORMATETC) error {
hr, _, err := i.Vtbl.GetCanonicalFormatEtc.Call(
uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(inputFormatEtc)),
uintptr(unsafe.Pointer(outputFormatEtc)),
)
if windows.Handle(hr) != windows.S_OK {
return syscall.Errno(hr)
}
return err
}
func (i *IDataObject) SetData(formatEtc *FORMATETC, medium *STGMEDIUM, release bool) error {
hr, _, err := i.Vtbl.SetData.Call(
uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(formatEtc)),
uintptr(unsafe.Pointer(medium)),
uintptr(BoolToBOOL(release)),
)
if windows.Handle(hr) != windows.S_OK {
return syscall.Errno(hr)
}
return err
}
func (i *IDataObject) EnumFormatEtc(dwDirection uint32, enumFormatEtc **IEnumFORMATETC) error {
hr, _, err := i.Vtbl.EnumFormatEtc.Call(
uintptr(unsafe.Pointer(i)),
uintptr(dwDirection),
uintptr(unsafe.Pointer(enumFormatEtc)),
)
if windows.Handle(hr) != windows.S_OK {
return syscall.Errno(hr)
}
return err
}
func (i *IDataObject) DAdvise(formatEtc *FORMATETC, advf uint32, adviseSink *IAdviseSink, pdwConnection *uint32) error {
hr, _, err := i.Vtbl.DAdvise.Call(
uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(formatEtc)),
uintptr(advf),
uintptr(unsafe.Pointer(adviseSink)),
uintptr(unsafe.Pointer(pdwConnection)),
)
if windows.Handle(hr) != windows.S_OK {
return syscall.Errno(hr)
}
return err
}
type DVTargetDevice struct {
TdSize uint32
TdDriverNameOffset uint16
TdDeviceNameOffset uint16
TdPortNameOffset uint16
TdExtDevmodeOffset uint16
TdData [1]byte
}
type FORMATETC struct {
CfFormat uint16
Ptd *DVTargetDevice
DwAspect uint32
Lindex int32
Tymed Tymed
}
type Tymed uint32
const (
TYMED_HGLOBAL Tymed = 1
TYMED_FILE Tymed = 2
TYMED_ISTREAM Tymed = 4
TYMED_ISTORAGE Tymed = 8
TYMED_GDI Tymed = 16
TYMED_MFPICT Tymed = 32
TYMED_ENHMF Tymed = 64
TYMED_NULL Tymed = 0
)
type STGMEDIUM struct {
Tymed Tymed
Union uintptr
PUnkForRelease IUnknownImpl
}
func (s STGMEDIUM) FileName() string {
if s.Tymed != TYMED_FILE {
return ""
}
return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(s.Union)))
}
func (s STGMEDIUM) Release() {
if s.PUnkForRelease != nil {
s.PUnkForRelease.Release()
}
}
type IEnumFORMATETC struct{}
type IAdviseSink struct{}
type IEnumStatData struct{}

45
v3/pkg/w32/idispatch.go Normal file
View file

@ -0,0 +1,45 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"unsafe"
)
type pIDispatchVtbl struct {
pQueryInterface uintptr
pAddRef uintptr
pRelease uintptr
pGetTypeInfoCount uintptr
pGetTypeInfo uintptr
pGetIDsOfNames uintptr
pInvoke uintptr
}
type IDispatch struct {
lpVtbl *pIDispatchVtbl
}
func (this *IDispatch) QueryInterface(id *GUID) *IDispatch {
return ComQueryInterface((*IUnknown)(unsafe.Pointer(this)), id)
}
func (this *IDispatch) AddRef() int32 {
return ComAddRef((*IUnknown)(unsafe.Pointer(this)))
}
func (this *IDispatch) Release() int32 {
return ComRelease((*IUnknown)(unsafe.Pointer(this)))
}
func (this *IDispatch) GetIDsOfName(names []string) []int32 {
return ComGetIDsOfName(this, names)
}
func (this *IDispatch) Invoke(dispid int32, dispatch int16, params ...interface{}) *VARIANT {
return ComInvoke(this, dispid, dispatch, params...)
}

140
v3/pkg/w32/idroptarget.go Normal file
View file

@ -0,0 +1,140 @@
//go:build windows
package w32
import (
"github.com/wailsapp/go-webview2/pkg/combridge"
"golang.org/x/sys/windows"
)
var (
DROPEFFECT_NONE DWORD = 0
DROPEFFECT_COPY DWORD = 1
DROPEFFECT_MOVE DWORD = 2
DROPEFFECT_LINK DWORD = 4
)
const (
DRAGDROP_E_ALREADYREGISTERED = 0x80040101
DRAGDROP_E_INVALIDHWND = 0x80040102
)
func _NOP(_ uintptr) uintptr {
return uintptr(windows.S_FALSE)
}
func init() {
combridge.RegisterVTable[combridge.IUnknown, iDropTarget](
"{00000122-0000-0000-C000-000000000046}",
_iDropTargetDragEnter,
_iDropTargetDragOver,
_iDropTargetDragLeave,
_iDropTargetDrop,
)
}
func _iDropTargetDragEnter(
this uintptr,
dataObject *IDataObject,
grfKeyState DWORD,
point POINT,
pdfEffect *DWORD,
) uintptr {
return combridge.Resolve[iDropTarget](this).DragEnter(dataObject, grfKeyState, point, pdfEffect)
}
func _iDropTargetDragOver(this uintptr, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr {
return combridge.Resolve[iDropTarget](this).DragOver(grfKeyState, point, pdfEffect)
}
func _iDropTargetDragLeave(this uintptr) uintptr {
return combridge.Resolve[iDropTarget](this).DragLeave()
}
func _iDropTargetDrop(this uintptr, dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr {
return combridge.Resolve[iDropTarget](this).Drop(dataObject, grfKeyState, point, pdfEffect)
}
type iDropTarget interface {
combridge.IUnknown
DragEnter(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr
DragOver(grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr
DragLeave() uintptr
Drop(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr
}
var _ iDropTarget = &DropTarget{}
type DropTarget struct {
combridge.IUnknownImpl
OnEnterEffect DWORD
OnOverEffect DWORD
OnEnter func()
OnLeave func()
OnOver func()
OnDrop func(filenames []string, x int, y int)
}
func NewDropTarget() *DropTarget {
result := &DropTarget{
OnEnterEffect: DROPEFFECT_COPY,
OnOverEffect: DROPEFFECT_COPY,
}
return result
}
func (d *DropTarget) DragEnter(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr {
*pdfEffect = d.OnEnterEffect
if d.OnEnter != nil {
d.OnEnter()
}
return uintptr(windows.S_OK)
}
func (d *DropTarget) DragOver(grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr {
*pdfEffect = d.OnOverEffect
if d.OnOver != nil {
d.OnOver()
}
return uintptr(windows.S_OK)
}
func (d *DropTarget) DragLeave() uintptr {
if d.OnLeave != nil {
d.OnLeave()
}
return uintptr(windows.S_OK)
}
func (d *DropTarget) Drop(dataObject *IDataObject, grfKeyState DWORD, point POINT, pdfEffect *DWORD) uintptr {
if d.OnDrop == nil {
return uintptr(windows.S_OK)
}
// Extract filenames from dataObject
var filenames []string
var formatETC = FORMATETC{
CfFormat: CF_HDROP,
Tymed: TYMED_HGLOBAL,
}
var stgMedium STGMEDIUM
err := dataObject.GetData(&formatETC, &stgMedium)
if err != nil && err != windows.ERROR_SUCCESS {
return uintptr(windows.S_FALSE)
}
defer stgMedium.Release()
hDrop := stgMedium.Union
_, numFiles := DragQueryFile(hDrop, 0xFFFFFFFF)
for i := uint(0); i < numFiles; i++ {
filename, _ := DragQueryFile(hDrop, i)
filenames = append(filenames, filename)
}
d.OnDrop(filenames, int(point.X), int(point.Y))
return uintptr(windows.S_OK)
}

55
v3/pkg/w32/image.go Normal file
View file

@ -0,0 +1,55 @@
//go:build windows
package w32
import (
"image"
"syscall"
"unsafe"
)
func CreateHBITMAPFromImage(img *image.RGBA) (HBITMAP, error) {
bounds := img.Bounds()
width, height := bounds.Dx(), bounds.Dy()
// Create a BITMAPINFO structure for the DIB
bmi := BITMAPINFO{
BmiHeader: BITMAPINFOHEADER{
BiSize: uint32(unsafe.Sizeof(BITMAPINFOHEADER{})),
BiWidth: int32(width),
BiHeight: int32(-height), // negative to indicate top-down bitmap
BiPlanes: 1,
BiBitCount: 32,
BiCompression: BI_RGB,
BiSizeImage: uint32(width * height * 4), // RGBA = 4 bytes
},
}
// Create the DIB section
var bits unsafe.Pointer
hbmp := CreateDIBSection(0, &bmi, DIB_RGB_COLORS, &bits, 0, 0)
if hbmp == 0 {
return 0, syscall.GetLastError()
}
// Copy the pixel data from the Go image to the DIB section
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
i := img.PixOffset(x, y)
r := img.Pix[i+0]
g := img.Pix[i+1]
b := img.Pix[i+2]
a := img.Pix[i+3]
// Write the RGBA pixel data to the DIB section (BGR order)
offset := y*width*4 + x*4
*((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 0))) = b
*((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 1))) = g
*((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 2))) = r
*((*uint8)(unsafe.Pointer(uintptr(bits) + uintptr(offset) + 3))) = a
}
}
return hbmp, nil
}

33
v3/pkg/w32/istream.go Normal file
View file

@ -0,0 +1,33 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"unsafe"
)
type pIStreamVtbl struct {
pQueryInterface uintptr
pAddRef uintptr
pRelease uintptr
}
type IStream struct {
lpVtbl *pIStreamVtbl
}
func (this *IStream) QueryInterface(id *GUID) *IDispatch {
return ComQueryInterface((*IUnknown)(unsafe.Pointer(this)), id)
}
func (this *IStream) AddRef() int32 {
return ComAddRef((*IUnknown)(unsafe.Pointer(this)))
}
func (this *IStream) Release() int32 {
return ComRelease((*IUnknown)(unsafe.Pointer(this)))
}

337
v3/pkg/w32/kernel32.go Normal file
View file

@ -0,0 +1,337 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetModuleHandle = modkernel32.NewProc("GetModuleHandleW")
procMulDiv = modkernel32.NewProc("MulDiv")
procGetConsoleWindow = modkernel32.NewProc("GetConsoleWindow")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procGetCurrentThreadId = modkernel32.NewProc("GetCurrentThreadId")
procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives")
procGetLogicalDriveStrings = modkernel32.NewProc("GetLogicalDriveStringsW")
procGetUserDefaultLCID = modkernel32.NewProc("GetUserDefaultLCID")
procLstrlen = modkernel32.NewProc("lstrlenW")
procLstrcpy = modkernel32.NewProc("lstrcpyW")
procGlobalAlloc = modkernel32.NewProc("GlobalAlloc")
procGlobalFree = modkernel32.NewProc("GlobalFree")
procGlobalLock = modkernel32.NewProc("GlobalLock")
procGlobalUnlock = modkernel32.NewProc("GlobalUnlock")
procMoveMemory = modkernel32.NewProc("RtlMoveMemory")
procFindResource = modkernel32.NewProc("FindResourceW")
procSizeofResource = modkernel32.NewProc("SizeofResource")
procLockResource = modkernel32.NewProc("LockResource")
procLoadResource = modkernel32.NewProc("LoadResource")
procGetLastError = modkernel32.NewProc("GetLastError")
procOpenProcess = modkernel32.NewProc("OpenProcess")
procTerminateProcess = modkernel32.NewProc("TerminateProcess")
procCloseHandle = modkernel32.NewProc("CloseHandle")
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
procModule32First = modkernel32.NewProc("Module32FirstW")
procModule32Next = modkernel32.NewProc("Module32NextW")
procGetSystemTimes = modkernel32.NewProc("GetSystemTimes")
procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo")
procSetConsoleTextAttribute = modkernel32.NewProc("SetConsoleTextAttribute")
procGetDiskFreeSpaceEx = modkernel32.NewProc("GetDiskFreeSpaceExW")
procGetProcessTimes = modkernel32.NewProc("GetProcessTimes")
procSetSystemTime = modkernel32.NewProc("SetSystemTime")
procGetSystemTime = modkernel32.NewProc("GetSystemTime")
)
func GetModuleHandle(modulename string) HINSTANCE {
var mn uintptr
if modulename == "" {
mn = 0
} else {
mn = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(modulename)))
}
ret, _, _ := procGetModuleHandle.Call(mn)
return HINSTANCE(ret)
}
func GetApplicationHandle() HINSTANCE {
ret, _, _ := procGetModuleHandle.Call(0)
return ret
}
func MulDiv(number, numerator, denominator int) int {
ret, _, _ := procMulDiv.Call(
uintptr(number),
uintptr(numerator),
uintptr(denominator))
return int(ret)
}
func GetConsoleWindow() HWND {
ret, _, _ := procGetConsoleWindow.Call()
return HWND(ret)
}
func GetCurrentThread() HANDLE {
ret, _, _ := procGetCurrentThread.Call()
return HANDLE(ret)
}
func GetCurrentThreadId() HANDLE {
ret, _, _ := procGetCurrentThreadId.Call()
return HANDLE(ret)
}
func GetLogicalDrives() uint32 {
ret, _, _ := procGetLogicalDrives.Call()
return uint32(ret)
}
func GetUserDefaultLCID() uint32 {
ret, _, _ := procGetUserDefaultLCID.Call()
return uint32(ret)
}
func Lstrlen(lpString *uint16) int {
ret, _, _ := procLstrlen.Call(uintptr(unsafe.Pointer(lpString)))
return int(ret)
}
func Lstrcpy(buf []uint16, lpString *uint16) {
procLstrcpy.Call(
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(lpString)))
}
func GlobalAlloc(uFlags uint, dwBytes uint32) HGLOBAL {
ret, _, _ := procGlobalAlloc.Call(
uintptr(uFlags),
uintptr(dwBytes))
if ret == 0 {
panic("GlobalAlloc failed")
}
return HGLOBAL(ret)
}
func GlobalFree(hMem HGLOBAL) {
ret, _, _ := procGlobalFree.Call(uintptr(hMem))
if ret != 0 {
panic("GlobalFree failed")
}
}
func GlobalLock(hMem HGLOBAL) unsafe.Pointer {
ret, _, _ := procGlobalLock.Call(uintptr(hMem))
if ret == 0 {
panic("GlobalLock failed")
}
return unsafe.Pointer(ret)
}
func GlobalUnlock(hMem HGLOBAL) bool {
ret, _, _ := procGlobalUnlock.Call(uintptr(hMem))
return ret != 0
}
func MoveMemory(destination, source unsafe.Pointer, length uint32) {
procMoveMemory.Call(
uintptr(unsafe.Pointer(destination)),
uintptr(source),
uintptr(length))
}
func FindResource(hModule HMODULE, lpName, lpType *uint16) (HRSRC, error) {
ret, _, _ := procFindResource.Call(
uintptr(hModule),
uintptr(unsafe.Pointer(lpName)),
uintptr(unsafe.Pointer(lpType)))
if ret == 0 {
return 0, syscall.GetLastError()
}
return HRSRC(ret), nil
}
func SizeofResource(hModule HMODULE, hResInfo HRSRC) uint32 {
ret, _, _ := procSizeofResource.Call(
uintptr(hModule),
uintptr(hResInfo))
if ret == 0 {
panic("SizeofResource failed")
}
return uint32(ret)
}
func LockResource(hResData HGLOBAL) unsafe.Pointer {
ret, _, _ := procLockResource.Call(uintptr(hResData))
if ret == 0 {
panic("LockResource failed")
}
return unsafe.Pointer(ret)
}
func LoadResource(hModule HMODULE, hResInfo HRSRC) HGLOBAL {
ret, _, _ := procLoadResource.Call(
uintptr(hModule),
uintptr(hResInfo))
if ret == 0 {
panic("LoadResource failed")
}
return HGLOBAL(ret)
}
func GetLastError() uint32 {
ret, _, _ := procGetLastError.Call()
return uint32(ret)
}
func OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) HANDLE {
inherit := 0
if inheritHandle {
inherit = 1
}
ret, _, _ := procOpenProcess.Call(
uintptr(desiredAccess),
uintptr(inherit),
uintptr(processId))
return HANDLE(ret)
}
func TerminateProcess(hProcess HANDLE, uExitCode uint) bool {
ret, _, _ := procTerminateProcess.Call(
uintptr(hProcess),
uintptr(uExitCode))
return ret != 0
}
func CloseHandle(object HANDLE) bool {
ret, _, _ := procCloseHandle.Call(
uintptr(object))
return ret != 0
}
func CreateToolhelp32Snapshot(flags, processId uint32) HANDLE {
ret, _, _ := procCreateToolhelp32Snapshot.Call(
uintptr(flags),
uintptr(processId))
if ret <= 0 {
return HANDLE(0)
}
return HANDLE(ret)
}
func Module32First(snapshot HANDLE, me *MODULEENTRY32) bool {
ret, _, _ := procModule32First.Call(
uintptr(snapshot),
uintptr(unsafe.Pointer(me)))
return ret != 0
}
func Module32Next(snapshot HANDLE, me *MODULEENTRY32) bool {
ret, _, _ := procModule32Next.Call(
uintptr(snapshot),
uintptr(unsafe.Pointer(me)))
return ret != 0
}
func GetSystemTimes(lpIdleTime, lpKernelTime, lpUserTime *FILETIME) bool {
ret, _, _ := procGetSystemTimes.Call(
uintptr(unsafe.Pointer(lpIdleTime)),
uintptr(unsafe.Pointer(lpKernelTime)),
uintptr(unsafe.Pointer(lpUserTime)))
return ret != 0
}
func GetProcessTimes(hProcess HANDLE, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime *FILETIME) bool {
ret, _, _ := procGetProcessTimes.Call(
uintptr(hProcess),
uintptr(unsafe.Pointer(lpCreationTime)),
uintptr(unsafe.Pointer(lpExitTime)),
uintptr(unsafe.Pointer(lpKernelTime)),
uintptr(unsafe.Pointer(lpUserTime)))
return ret != 0
}
func GetConsoleScreenBufferInfo(hConsoleOutput HANDLE) *CONSOLE_SCREEN_BUFFER_INFO {
var csbi CONSOLE_SCREEN_BUFFER_INFO
ret, _, _ := procGetConsoleScreenBufferInfo.Call(
uintptr(hConsoleOutput),
uintptr(unsafe.Pointer(&csbi)))
if ret == 0 {
return nil
}
return &csbi
}
func SetConsoleTextAttribute(hConsoleOutput HANDLE, wAttributes uint16) bool {
ret, _, _ := procSetConsoleTextAttribute.Call(
uintptr(hConsoleOutput),
uintptr(wAttributes))
return ret != 0
}
func GetDiskFreeSpaceEx(dirName string) (r bool,
freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64) {
ret, _, _ := procGetDiskFreeSpaceEx.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(dirName))),
uintptr(unsafe.Pointer(&freeBytesAvailable)),
uintptr(unsafe.Pointer(&totalNumberOfBytes)),
uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)))
return ret != 0,
freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes
}
func GetSystemTime() *SYSTEMTIME {
var time SYSTEMTIME
procGetSystemTime.Call(
uintptr(unsafe.Pointer(&time)))
return &time
}
func SetSystemTime(time *SYSTEMTIME) bool {
ret, _, _ := procSetSystemTime.Call(
uintptr(unsafe.Pointer(time)))
return ret != 0
}
func GetLogicalDriveStrings(nBufferLength uint32, lpBuffer *uint16) uint32 {
ret, _, _ := procGetLogicalDriveStrings.Call(
uintptr(nBufferLength),
uintptr(unsafe.Pointer(lpBuffer)),
0)
return uint32(ret)
}

980
v3/pkg/w32/menubar.go Normal file
View file

@ -0,0 +1,980 @@
//go:build windows
package w32
import (
"os"
"unsafe"
)
const (
OBJID_MENU = -3
ODT_MENU = 1
// Menu info flags
MIIM_BACKGROUND = 0x00000002
MIIM_APPLYTOSUBMENUS = 0x80000000
)
var (
menuTheme HTHEME
procSetMenuInfo = moduser32.NewProc("SetMenuInfo")
)
type DTTOPTS struct {
DwSize uint32
DwFlags uint32
CrText uint32
CrBorder uint32
CrShadow uint32
ITextShadowType int32
PtShadowOffset POINT
iBorderSize int32
iFontPropId int32
IColorPropId int32
IStateId int32
FApplyOverlay int32
IGlowSize int32
PfnDrawTextCallback uintptr
LParam uintptr
}
const (
MENU_POPUPITEM = 14
MENU_BARITEM = 8 // Menu bar item part ID for theme drawing
DTT_TEXTCOLOR = 1
)
// Menu item states
const (
ODS_SELECTED = 0x0001
ODS_GRAYED = 0x0002
ODS_DISABLED = 0x0004
ODS_CHECKED = 0x0008
ODS_FOCUS = 0x0010
ODS_DEFAULT = 0x0020
ODS_HOTLIGHT = 0x0040
ODS_INACTIVE = 0x0080
ODS_NOACCEL = 0x0100
ODS_NOFOCUSRECT = 0x0200
)
// Menu Button Image states
const (
MBI_NORMAL = 1
MBI_HOT = 2
MBI_PUSHED = 3
MBI_DISABLED = 4
)
var (
procGetMenuItemInfo = moduser32.NewProc("GetMenuItemInfoW")
procGetMenuItemCount = moduser32.NewProc("GetMenuItemCount")
procGetMenuItemRect = moduser32.NewProc("GetMenuItemRect")
)
func GetMenuItemInfo(hmenu HMENU, item uint32, fByPosition bool, lpmii *MENUITEMINFO) bool {
ret, _, _ := procGetMenuItemInfo.Call(
uintptr(hmenu),
uintptr(item),
uintptr(boolToUint(fByPosition)),
uintptr(unsafe.Pointer(lpmii)),
)
return ret != 0
}
func GetMenuItemCount(hmenu HMENU) int {
ret, _, _ := procGetMenuItemCount.Call(uintptr(hmenu))
return int(ret)
}
func GetMenuItemRect(hwnd HWND, hmenu HMENU, item uint32, rect *RECT) bool {
ret, _, _ := procGetMenuItemRect.Call(
uintptr(hwnd),
uintptr(hmenu),
uintptr(item),
uintptr(unsafe.Pointer(rect)),
)
return ret != 0
}
// Helper function to convert bool to uint
func boolToUint(b bool) uint {
if b {
return 1
}
return 0
}
type UAHMENU struct {
Hmenu HMENU
Hdc HDC
DwFlags uint32
}
type MENUBARINFO struct {
CbSize uint32
Bar RECT
Menu HMENU
Window HWND
BarFocused int32
Focused int32
}
type DRAWITEMSTRUCT struct {
ControlType uint32
ControlID uint32
ItemID uint32
ItemAction uint32
ItemState uint32
HWNDItem HWND
HDC HDC
RcItem RECT
ItemData uintptr
}
type UAHDRAWMENUITEM struct {
DIS DRAWITEMSTRUCT
UM UAHMENU
UAMI UAHMENUITEM
}
type UAHMENUITEM struct {
Position int
Umim UAHMENUITEMMETRICS
Umpm UAHMENUPOPUPMETRICS
}
type UAHMENUITEMMETRICS struct {
data [32]byte // Total size of the union in bytes (4 DWORDs * 4 bytes each * 2 arrays)
}
func (u *UAHMENUITEMMETRICS) RgsizeBar() *[2]struct{ cx, cy uint32 } {
return (*[2]struct{ cx, cy uint32 })(unsafe.Pointer(&u.data))
}
func (u *UAHMENUITEMMETRICS) RgsizePopup() *[4]struct{ cx, cy uint32 } {
return (*[4]struct{ cx, cy uint32 })(unsafe.Pointer(&u.data))
}
type UAHMEASUREMENUITEM struct {
UM UAHMENU
UAMI UAHMENUITEM
Mis MEASUREITEMSTRUCT
}
type MEASUREITEMSTRUCT struct {
CtlType uint32
CtlID uint32
ItemID uint32
ItemWidth uint32
ItemHeight uint32
ItemData uintptr
}
type UAHMENUPOPUPMETRICS struct {
Rgcx [4]uint32 // Array of 4 DWORDs
FUpdateMaxWidths uint32 // Bit-field represented as a uint32
}
// Helper function to get the value of the fUpdateMaxWidths bit-field
func (u *UAHMENUPOPUPMETRICS) GetFUpdateMaxWidths() uint32 {
return u.FUpdateMaxWidths & 0x3 // Mask to get the first 2 bits
}
// Helper function to set the value of the fUpdateMaxWidths bit-field
func (u *UAHMENUPOPUPMETRICS) SetFUpdateMaxWidths(value uint32) {
u.FUpdateMaxWidths = (u.FUpdateMaxWidths &^ 0x3) | (value & 0x3) // Clear and set the first 2 bits
}
type MenuBarTheme struct {
TitleBarBackground *uint32
TitleBarText *uint32
MenuBarBackground *uint32 // Separate color for menubar
MenuHoverBackground *uint32
MenuHoverText *uint32
MenuSelectedBackground *uint32
MenuSelectedText *uint32
// private brushes
titleBarBackgroundBrush HBRUSH
menuBarBackgroundBrush HBRUSH // Separate brush for menubar
menuHoverBackgroundBrush HBRUSH
menuSelectedBackgroundBrush HBRUSH
}
func createColourWithDefaultColor(color *uint32, def uint32) *uint32 {
if color == nil {
return &def
}
return color
}
func (d *MenuBarTheme) Init() {
d.TitleBarBackground = createColourWithDefaultColor(d.TitleBarBackground, RGB(25, 25, 26))
d.TitleBarText = createColourWithDefaultColor(d.TitleBarText, RGB(222, 222, 222))
d.MenuBarBackground = createColourWithDefaultColor(d.MenuBarBackground, RGB(33, 33, 33))
d.MenuSelectedText = createColourWithDefaultColor(d.MenuSelectedText, RGB(222, 222, 222))
d.MenuSelectedBackground = createColourWithDefaultColor(d.MenuSelectedBackground, RGB(48, 48, 48))
d.MenuHoverText = createColourWithDefaultColor(d.MenuHoverText, RGB(222, 222, 222))
d.MenuHoverBackground = createColourWithDefaultColor(d.MenuHoverBackground, RGB(48, 48, 48))
// Create brushes
d.titleBarBackgroundBrush = CreateSolidBrush(*d.TitleBarBackground)
d.menuBarBackgroundBrush = CreateSolidBrush(*d.MenuBarBackground)
d.menuHoverBackgroundBrush = CreateSolidBrush(*d.MenuHoverBackground)
d.menuSelectedBackgroundBrush = CreateSolidBrush(*d.MenuSelectedBackground)
}
// SetMenuBackground sets the menu background brush directly
func (d *MenuBarTheme) SetMenuBackground(hmenu HMENU) {
var mi MENUINFO
mi.CbSize = uint32(unsafe.Sizeof(mi))
mi.FMask = MIIM_BACKGROUND | MIIM_APPLYTOSUBMENUS
mi.HbrBack = d.menuBarBackgroundBrush // Use separate menubar brush
SetMenuInfo(hmenu, &mi)
}
// SetMenuInfo wrapper function
func SetMenuInfo(hmenu HMENU, lpcmi *MENUINFO) bool {
ret, _, _ := procSetMenuInfo.Call(
uintptr(hmenu),
uintptr(unsafe.Pointer(lpcmi)))
return ret != 0
}
func CreateSolidBrush(color COLORREF) HBRUSH {
ret, _, _ := procCreateSolidBrush.Call(
uintptr(color),
)
return HBRUSH(ret)
}
func RGB(r, g, b byte) uint32 {
return uint32(r) | uint32(g)<<8 | uint32(b)<<16
}
func RGBptr(r, g, b byte) *uint32 {
result := uint32(r) | uint32(g)<<8 | uint32(b)<<16
return &result
}
// Track hover state for menubar items when maximized
var (
currentHoverItem int = -1
menuIsOpen bool = false // Track if a dropdown menu is open
)
func MenuBarWndProc(hwnd HWND, msg uint32, wParam WPARAM, lParam LPARAM, theme *MenuBarTheme) (bool, LRESULT) {
// Only proceed if we have a theme (either for dark or light mode)
if theme == nil {
return false, 0
}
switch msg {
case WM_UAHDRAWMENU:
udm := (*UAHMENU)(unsafe.Pointer(lParam))
// Check if maximized first
isMaximized := IsZoomed(hwnd)
// get the menubar rect
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if !GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
return false, 0
}
winRect := GetWindowRect(hwnd)
// the rcBar is offset by the window rect
rc := menuBarInfo.Bar
OffsetRect(&rc, int(-winRect.Left), int(-winRect.Top))
// DEBUG: Log the coordinates
// println("WM_UAHDRAWMENU: maximized=", isMaximized)
// println(" menubar screen rect: L=", menuBarInfo.Bar.Left, "T=", menuBarInfo.Bar.Top,
// "R=", menuBarInfo.Bar.Right, "B=", menuBarInfo.Bar.Bottom)
// println(" window rect: L=", winRect.Left, "T=", winRect.Top,
// "R=", winRect.Right, "B=", winRect.Bottom)
// println(" converted rect: L=", rc.Left, "T=", rc.Top,
// "R=", rc.Right, "B=", rc.Bottom)
// When maximized, Windows extends the window beyond the visible area
// We need to adjust the menubar rect to ensure it's fully visible
if isMaximized {
// Get the frame size - this is how much the window extends beyond visible area when maximized
frameY := GetSystemMetrics(SM_CYSIZEFRAME)
paddedBorder := GetSystemMetrics(SM_CXPADDEDBORDER)
// In Windows 10/11, the actual border is frame + padding
borderSize := frameY + paddedBorder
// println(" Frame metrics: frameY=", frameY, "paddedBorder=", paddedBorder, "borderSize=", borderSize)
// First, fill the area from the top of the visible area to the menubar
topFillRect := RECT{
Left: rc.Left,
Top: int32(borderSize), // Start of visible area in window coordinates
Right: rc.Right,
Bottom: rc.Top, // Up to where the menubar starts
}
FillRect(udm.Hdc, &topFillRect, theme.menuBarBackgroundBrush)
}
// Fill the entire menubar background with dark color
FillRect(udm.Hdc, &rc, theme.menuBarBackgroundBrush)
// Paint over the menubar border explicitly
// The border is typically 1-2 pixels at the bottom
borderRect := rc
borderRect.Top = borderRect.Bottom - 1
borderRect.Bottom = borderRect.Bottom + 2
FillRect(udm.Hdc, &borderRect, theme.menuBarBackgroundBrush)
// When maximized, we still need to handle the drawing ourselves
// Some projects found that returning false here causes issues
// When maximized, manually draw all menu items here
if isMaximized {
// Draw each menu item manually
itemCount := GetMenuItemCount(menuBarInfo.Menu)
for i := 0; i < itemCount; i++ {
var itemRect RECT
if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(i), &itemRect) {
// Convert to window coordinates
OffsetRect(&itemRect, int(-winRect.Left), int(-winRect.Top))
// Check if this item is hovered
if i == currentHoverItem {
// Fill with hover background
FillRect(udm.Hdc, &itemRect, theme.menuHoverBackgroundBrush)
}
// Get menu text
menuString := make([]uint16, 256)
mii := MENUITEMINFO{
CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})),
FMask: MIIM_STRING,
DwTypeData: &menuString[0],
Cch: uint32(len(menuString) - 1),
}
if GetMenuItemInfo(menuBarInfo.Menu, uint32(i), true, &mii) {
// Draw the text
if i == currentHoverItem {
SetTextColor(udm.Hdc, COLORREF(*theme.MenuHoverText))
} else {
SetTextColor(udm.Hdc, COLORREF(*theme.TitleBarText))
}
SetBkMode(udm.Hdc, TRANSPARENT)
DrawText(udm.Hdc, menuString, -1, &itemRect, DT_CENTER|DT_SINGLELINE|DT_VCENTER)
}
}
}
}
// Return the original HDC so Windows can draw the menu text
return true, LRESULT(udm.Hdc)
case WM_DRAWITEM:
// Handle owner-drawn menu items
dis := (*DRAWITEMSTRUCT)(unsafe.Pointer(lParam))
// Check if this is a menu item
if dis.ControlType == ODT_MENU {
// Draw the menu item background
var bgBrush HBRUSH
var textColor uint32
if dis.ItemState&ODS_SELECTED != 0 {
// Selected state
bgBrush = theme.menuSelectedBackgroundBrush
textColor = *theme.MenuSelectedText
} else {
// Normal state
bgBrush = theme.titleBarBackgroundBrush
textColor = *theme.TitleBarText
}
// Fill background
FillRect(dis.HDC, &dis.RcItem, bgBrush)
// Draw text if we have item data
if dis.ItemData != 0 {
text := (*uint16)(unsafe.Pointer(dis.ItemData))
if text != nil {
// Set text color and draw
SetTextColor(dis.HDC, COLORREF(textColor))
SetBkMode(dis.HDC, TRANSPARENT)
DrawText(dis.HDC, (*[256]uint16)(unsafe.Pointer(text))[:], -1, &dis.RcItem, DT_CENTER|DT_SINGLELINE|DT_VCENTER)
}
}
return true, 1
}
case WM_UAHDRAWMENUITEM:
udmi := (*UAHDRAWMENUITEM)(unsafe.Pointer(lParam))
// Check if we're getting menu item draw messages when maximized or fullscreen
isMaximized := IsZoomed(hwnd)
// Create buffer for menu text
menuString := make([]uint16, 256)
// Setup menu item info structure
mii := MENUITEMINFO{
CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})),
FMask: MIIM_STRING | MIIM_SUBMENU,
DwTypeData: &menuString[0],
Cch: uint32(len(menuString) - 1),
}
if !GetMenuItemInfo(udmi.UM.Hmenu, uint32(udmi.UAMI.Position), true, &mii) {
// Failed to get menu item info, let default handler process
return false, 0
}
// Remove automatic popup on hover - menus should only open on click
// This was causing the menu to appear at wrong coordinates
dwFlags := uint32(DT_CENTER | DT_SINGLELINE | DT_VCENTER)
// When maximized/fullscreen, try without VCENTER to see if text appears
if isMaximized && os.Getenv("WAILS_TEST_NO_VCENTER") == "1" {
dwFlags = uint32(DT_CENTER | DT_SINGLELINE)
println(" Using dwFlags without VCENTER")
}
// Check if this is a menubar item
// When dwFlags has 0x0A00 (2560) it's a menubar item
isMenuBarItem := (udmi.UM.DwFlags&0x0A00) == 0x0A00 || udmi.UM.DwFlags == 0
// Use different colors for menubar vs popup items
var bgBrush HBRUSH
var textColor uint32
if udmi.DIS.ItemState&ODS_HOTLIGHT != 0 {
// Hot state - use a specific color for hover
bgBrush = theme.menuHoverBackgroundBrush
textColor = *theme.MenuHoverText
} else if udmi.DIS.ItemState&ODS_SELECTED != 0 {
// Selected state
bgBrush = theme.menuSelectedBackgroundBrush
textColor = *theme.MenuSelectedText
} else {
// Normal state
if isMenuBarItem {
// Menubar items in normal state
bgBrush = theme.menuBarBackgroundBrush
textColor = *theme.TitleBarText
} else {
// Popup menu items in normal state - use same color as menubar
bgBrush = theme.menuBarBackgroundBrush
textColor = *theme.TitleBarText
}
}
// Fill background
if bgBrush != 0 {
FillRect(udmi.UM.Hdc, &udmi.DIS.RcItem, bgBrush)
}
// Draw text
SetTextColor(udmi.UM.Hdc, COLORREF(textColor))
SetBkMode(udmi.UM.Hdc, TRANSPARENT)
// When maximized/fullscreen and menubar item, use the same font settings as drawMenuBarText
if isMaximized && isMenuBarItem {
// Create a non-bold font explicitly
menuFont := LOGFONT{
Height: -12, // Standard Windows menu font height (9pt)
Weight: 400, // FW_NORMAL (not bold)
CharSet: 1, // DEFAULT_CHARSET
Quality: 5, // CLEARTYPE_QUALITY
PitchAndFamily: 0, // DEFAULT_PITCH
}
// Set font face name to "Segoe UI" (Windows default)
fontName := []uint16{'S', 'e', 'g', 'o', 'e', ' ', 'U', 'I', 0}
copy(menuFont.FaceName[:], fontName)
hFont := CreateFontIndirect(&menuFont)
if hFont != 0 {
oldFont := SelectObject(udmi.UM.Hdc, HGDIOBJ(hFont))
DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags)
SelectObject(udmi.UM.Hdc, oldFont)
DeleteObject(HGDIOBJ(hFont))
} else {
DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags)
}
return true, 4 // CDRF_SKIPDEFAULT
} else {
DrawText(udmi.UM.Hdc, menuString, -1, &udmi.DIS.RcItem, dwFlags)
}
// Return appropriate value based on whether we're in maximized/fullscreen
// For maximized, we need to ensure Windows doesn't override our drawing
if isMaximized {
// Skip default processing to prevent Windows from overriding our colors
return true, 4 // CDRF_SKIPDEFAULT
}
// Return 1 to indicate we've handled the drawing
return true, 1
case WM_UAHMEASUREMENUITEM:
// Let the default window procedure handle the menu item measurement
// We're not modifying the default sizing anymore
result := DefWindowProc(hwnd, msg, wParam, lParam)
return true, result
case WM_NCPAINT:
// Paint our custom menubar first
paintDarkMenuBar(hwnd, theme)
// Then let Windows do its default painting
result := DefWindowProc(hwnd, msg, wParam, lParam)
// Paint again to ensure our painting is on top
paintDarkMenuBar(hwnd, theme)
return true, result
case WM_NCACTIVATE:
result := DefWindowProc(hwnd, msg, wParam, lParam)
// Force paint the menubar with dark background
paintDarkMenuBar(hwnd, theme)
return false, result
case WM_PAINT:
// Let Windows paint first
result := DefWindowProc(hwnd, msg, wParam, lParam)
// Then paint our menubar
paintDarkMenuBar(hwnd, theme)
return false, result
case WM_ACTIVATEAPP, WM_ACTIVATE:
// Handle app activation/deactivation
result := DefWindowProc(hwnd, msg, wParam, lParam)
// Repaint menubar
paintDarkMenuBar(hwnd, theme)
return false, result
case WM_SIZE, WM_WINDOWPOSCHANGED:
// Handle window size changes
result := DefWindowProc(hwnd, msg, wParam, lParam)
// Repaint menubar after size change
paintDarkMenuBar(hwnd, theme)
// CRITICAL: Force complete menubar redraw when maximized
if msg == WM_SIZE && wParam == SIZE_MAXIMIZED {
// Invalidate the entire menubar area to force redraw
var mbi MENUBARINFO
mbi.CbSize = uint32(unsafe.Sizeof(mbi))
if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &mbi) {
InvalidateRect(hwnd, &mbi.Bar, true)
DrawMenuBar(hwnd)
}
}
return false, result
case WM_SETFOCUS, WM_KILLFOCUS:
// Handle focus changes (e.g., when inspector opens)
result := DefWindowProc(hwnd, msg, wParam, lParam)
// Repaint menubar after focus change
paintDarkMenuBar(hwnd, theme)
return false, result
case WM_ERASEBKGND:
// When maximized, draw menubar text here
if IsZoomed(hwnd) {
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
hdc := HDC(wParam)
drawMenuBarText(hwnd, hdc, &menuBarInfo, theme)
}
}
return false, 0
case WM_NCMOUSEMOVE, WM_MOUSEMOVE:
// Track mouse movement for hover effects when maximized
if IsZoomed(hwnd) {
// Don't process hover changes while menu is open
if menuIsOpen {
return false, 0
}
var screenX, screenY int32
if msg == WM_NCMOUSEMOVE {
// For NC messages, lParam contains screen coordinates
screenX = int32(LOWORD(uint32(lParam)))
screenY = int32(HIWORD(uint32(lParam)))
} else {
// For regular MOUSEMOVE, convert client to screen coordinates
clientX := int32(LOWORD(uint32(lParam)))
clientY := int32(HIWORD(uint32(lParam)))
sx, sy := ClientToScreen(hwnd, int(clientX), int(clientY))
screenX = int32(sx)
screenY = int32(sy)
}
// Check if we're over the menubar
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
// menuBarInfo.Bar already contains screen coordinates
// Check if mouse is over menubar using screen coordinates
if screenX >= menuBarInfo.Bar.Left && screenX <= menuBarInfo.Bar.Right &&
screenY >= menuBarInfo.Bar.Top && screenY <= menuBarInfo.Bar.Bottom {
// Always re-request mouse tracking to ensure we get leave messages
TrackMouseEvent(&TRACKMOUSEEVENT{
CbSize: uint32(unsafe.Sizeof(TRACKMOUSEEVENT{})),
DwFlags: TME_LEAVE | TME_NONCLIENT,
HwndTrack: hwnd,
DwHoverTime: 0,
})
// Find which menu item we're over
itemCount := GetMenuItemCount(menuBarInfo.Menu)
newHoverItem := -1
for i := 0; i < itemCount; i++ {
var itemRect RECT
if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(i), &itemRect) {
// itemRect is already in screen coordinates from GetMenuItemRect
// Check using screen coordinates
if screenX >= itemRect.Left && screenX <= itemRect.Right &&
screenY >= itemRect.Top && screenY <= itemRect.Bottom {
newHoverItem = i
break
}
}
}
// If hover item changed, update and redraw just the menubar
if newHoverItem != currentHoverItem {
currentHoverItem = newHoverItem
// Get the actual menubar rect for precise invalidation
winRect := GetWindowRect(hwnd)
menubarRect := menuBarInfo.Bar
// Convert to window coordinates
menubarRect.Left -= winRect.Left
menubarRect.Top -= winRect.Top
menubarRect.Right -= winRect.Left
menubarRect.Bottom -= winRect.Top
// Invalidate only the menubar
InvalidateRect(hwnd, &menubarRect, false)
}
} else {
// Mouse left menubar
if currentHoverItem != -1 {
currentHoverItem = -1
// Get the actual menubar rect
winRect := GetWindowRect(hwnd)
menubarRect := menuBarInfo.Bar
// Convert to window coordinates
menubarRect.Left -= winRect.Left
menubarRect.Top -= winRect.Top
menubarRect.Right -= winRect.Left
menubarRect.Bottom -= winRect.Top
InvalidateRect(hwnd, &menubarRect, false)
}
}
}
}
return false, 0
case WM_NCLBUTTONDOWN:
// When clicking on menubar, clear hover state immediately
if IsZoomed(hwnd) && currentHoverItem != -1 {
// Check if click is on menubar
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
// Get click position (screen coordinates)
clickX := int32(LOWORD(uint32(lParam)))
clickY := int32(HIWORD(uint32(lParam)))
if clickX >= menuBarInfo.Bar.Left && clickX <= menuBarInfo.Bar.Right &&
clickY >= menuBarInfo.Bar.Top && clickY <= menuBarInfo.Bar.Bottom {
// Click is on menubar - clear hover
currentHoverItem = -1
}
}
}
return false, 0
case WM_NCMOUSELEAVE, WM_MOUSELEAVE:
// Clear hover state when mouse leaves (but not if menu is open)
if IsZoomed(hwnd) && currentHoverItem != -1 && !menuIsOpen {
currentHoverItem = -1
// Get menubar info for precise invalidation
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
winRect := GetWindowRect(hwnd)
menubarRect := menuBarInfo.Bar
menubarRect.Left -= winRect.Left
menubarRect.Top -= winRect.Top
menubarRect.Right -= winRect.Left
menubarRect.Bottom -= winRect.Top
InvalidateRect(hwnd, &menubarRect, false)
}
}
return false, 0
case WM_ENTERMENULOOP:
// Menu is being opened - clear hover state
menuIsOpen = true
if IsZoomed(hwnd) && currentHoverItem != -1 {
oldHoverItem := currentHoverItem
currentHoverItem = -1
// Redraw the previously hovered item to remove hover effect
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
var itemRect RECT
if GetMenuItemRect(hwnd, menuBarInfo.Menu, uint32(oldHoverItem), &itemRect) {
winRect := GetWindowRect(hwnd)
// Convert to window coordinates
itemRect.Left -= winRect.Left
itemRect.Top -= winRect.Top
itemRect.Right -= winRect.Left
itemRect.Bottom -= winRect.Top
// Add some padding
itemRect.Left -= 5
itemRect.Right += 5
itemRect.Top -= 5
itemRect.Bottom += 5
InvalidateRect(hwnd, &itemRect, false)
}
}
}
return false, 0
case WM_EXITMENULOOP:
// Menu has been closed
menuIsOpen = false
// Clear any existing hover state first
currentHoverItem = -1
// Force a complete menubar redraw
if IsZoomed(hwnd) {
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
winRect := GetWindowRect(hwnd)
menubarRect := menuBarInfo.Bar
menubarRect.Left -= winRect.Left
menubarRect.Top -= winRect.Top
menubarRect.Right -= winRect.Left
menubarRect.Bottom -= winRect.Top
InvalidateRect(hwnd, &menubarRect, false)
}
// Force a timer to restart mouse tracking
SetTimer(hwnd, 1001, 50, 0)
}
return false, 0
case WM_TIMER:
// Handle our mouse tracking restart timer
if wParam == 1001 {
KillTimer(hwnd, 1001)
if IsZoomed(hwnd) {
// Get current mouse position and simulate a mouse move
x, y, _ := GetCursorPos()
// Check if mouse is over the window
winRect := GetWindowRect(hwnd)
if x >= int(winRect.Left) && x <= int(winRect.Right) &&
y >= int(winRect.Top) && y <= int(winRect.Bottom) {
// Check if we're over the menubar specifically
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
if int32(x) >= menuBarInfo.Bar.Left && int32(x) <= menuBarInfo.Bar.Right &&
int32(y) >= menuBarInfo.Bar.Top && int32(y) <= menuBarInfo.Bar.Bottom {
// Post a non-client mouse move to restart tracking
PostMessage(hwnd, WM_NCMOUSEMOVE, 0, uintptr(y)<<16|uintptr(x)&0xFFFF)
} else {
// Convert to client coordinates for regular mouse move
clientX, clientY, _ := ScreenToClient(hwnd, x, y)
// Post a mouse move message to restart tracking
PostMessage(hwnd, WM_MOUSEMOVE, 0, uintptr(clientY)<<16|uintptr(clientX)&0xFFFF)
}
}
}
}
return true, 0
}
return false, 0
}
return false, 0
}
// paintDarkMenuBar paints the menubar with dark background
func paintDarkMenuBar(hwnd HWND, theme *MenuBarTheme) {
// Get menubar info
var menuBarInfo MENUBARINFO
menuBarInfo.CbSize = uint32(unsafe.Sizeof(menuBarInfo))
if !GetMenuBarInfo(hwnd, OBJID_MENU, 0, &menuBarInfo) {
return
}
// Get window DC
hdc := GetWindowDC(hwnd)
if hdc == 0 {
return
}
defer ReleaseDC(hwnd, hdc)
// Check if window is maximized or fullscreen
isMaximized := IsZoomed(hwnd)
isFullscreen := false
// Check if window is in fullscreen by checking if it covers the monitor
windowRect := GetWindowRect(hwnd)
monitor := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY)
var monitorInfo MONITORINFO
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
if GetMonitorInfo(monitor, &monitorInfo) {
// If window matches monitor bounds, it's fullscreen
if windowRect.Left == monitorInfo.RcMonitor.Left &&
windowRect.Top == monitorInfo.RcMonitor.Top &&
windowRect.Right == monitorInfo.RcMonitor.Right &&
windowRect.Bottom == monitorInfo.RcMonitor.Bottom {
isFullscreen = true
}
}
// When maximized or fullscreen, we need to handle the special case
if isMaximized || isFullscreen {
// Convert menubar rect from screen to window coordinates
menubarRect := menuBarInfo.Bar
menubarRect.Left -= windowRect.Left
menubarRect.Top -= windowRect.Top
menubarRect.Right -= windowRect.Left
menubarRect.Bottom -= windowRect.Top
if isMaximized && !isFullscreen {
// Get the frame size (only for maximized, not fullscreen)
frameY := GetSystemMetrics(SM_CYSIZEFRAME)
paddedBorder := GetSystemMetrics(SM_CXPADDEDBORDER)
borderSize := frameY + paddedBorder
// Fill from visible area top to menubar
topFillRect := RECT{
Left: menubarRect.Left,
Top: int32(borderSize), // Start of visible area
Right: menubarRect.Right,
Bottom: menubarRect.Top,
}
FillRect(hdc, &topFillRect, theme.menuBarBackgroundBrush)
} else if isFullscreen {
// In fullscreen, fill from the very top
topFillRect := RECT{
Left: menubarRect.Left,
Top: 0, // Start from top in fullscreen
Right: menubarRect.Right,
Bottom: menubarRect.Top,
}
FillRect(hdc, &topFillRect, theme.menuBarBackgroundBrush)
}
// Fill the menubar itself
FillRect(hdc, &menubarRect, theme.menuBarBackgroundBrush)
} else {
// Paint the menubar background with dark color
FillRect(hdc, &menuBarInfo.Bar, theme.menuBarBackgroundBrush)
}
// Get window and client rects to find the non-client area
clientRect := GetClientRect(hwnd)
// Convert client rect top-left to screen coordinates
_, screenY := ClientToScreen(hwnd, int(clientRect.Left), int(clientRect.Top))
// Paint the entire area between menubar and client area
// This should cover any borders
borderRect := RECT{
Left: 0,
Top: menuBarInfo.Bar.Bottom - windowRect.Top,
Right: windowRect.Right - windowRect.Left,
Bottom: int32(screenY) - windowRect.Top,
}
FillRect(hdc, &borderRect, theme.menuBarBackgroundBrush)
// When maximized or fullscreen, also draw menubar text
if isMaximized || isFullscreen {
drawMenuBarText(hwnd, hdc, &menuBarInfo, theme)
}
}
func drawMenuBarText(hwnd HWND, hdc HDC, menuBarInfo *MENUBARINFO, theme *MenuBarTheme) {
// Get the menu handle
hmenu := menuBarInfo.Menu
if hmenu == 0 {
return
}
// Get the number of menu items
itemCount := GetMenuItemCount(hmenu)
if itemCount <= 0 {
return
}
// Create a non-bold font explicitly
menuFont := LOGFONT{
Height: -12, // Standard Windows menu font height (9pt)
Weight: 400, // FW_NORMAL (not bold)
CharSet: 1, // DEFAULT_CHARSET
Quality: 5, // CLEARTYPE_QUALITY
PitchAndFamily: 0, // DEFAULT_PITCH
}
// Set font face name to "Segoe UI" (Windows default)
fontName := []uint16{'S', 'e', 'g', 'o', 'e', ' ', 'U', 'I', 0}
copy(menuFont.FaceName[:], fontName)
hFont := CreateFontIndirect(&menuFont)
if hFont != 0 {
oldFont := SelectObject(hdc, HGDIOBJ(hFont))
defer func() {
SelectObject(hdc, oldFont)
DeleteObject(HGDIOBJ(hFont))
}()
}
// Set text color and background mode
SetTextColor(hdc, COLORREF(*theme.TitleBarText))
SetBkMode(hdc, TRANSPARENT)
// Get the window rect for coordinate conversion
winRect := GetWindowRect(hwnd)
// Iterate through each menu item
for i := 0; i < itemCount; i++ {
// Get the menu item rect
var itemRect RECT
if !GetMenuItemRect(hwnd, hmenu, uint32(i), &itemRect) {
continue
}
// Convert to window coordinates
OffsetRect(&itemRect, int(-winRect.Left), int(-winRect.Top))
// Check if this item is hovered
if i == currentHoverItem {
// Fill with hover background
FillRect(hdc, &itemRect, theme.menuHoverBackgroundBrush)
}
// Get the menu item text
menuString := make([]uint16, 256)
mii := MENUITEMINFO{
CbSize: uint32(unsafe.Sizeof(MENUITEMINFO{})),
FMask: MIIM_STRING,
DwTypeData: &menuString[0],
Cch: uint32(len(menuString) - 1),
}
if GetMenuItemInfo(hmenu, uint32(i), true, &mii) {
// Set text color based on hover state
if i == currentHoverItem {
SetTextColor(hdc, COLORREF(*theme.MenuHoverText))
} else {
SetTextColor(hdc, COLORREF(*theme.TitleBarText))
}
// Draw the text
DrawText(hdc, menuString, -1, &itemRect, DT_CENTER|DT_SINGLELINE|DT_VCENTER)
}
}
}

119
v3/pkg/w32/ole32.go Normal file
View file

@ -0,0 +1,119 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
"github.com/wailsapp/go-webview2/pkg/combridge"
)
var (
modole32 = syscall.NewLazyDLL("ole32.dll")
procCoInitializeEx = modole32.NewProc("CoInitializeEx")
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")
)
func CoInitializeEx(coInit uintptr) HRESULT {
ret, _, _ := procCoInitializeEx.Call(
0,
coInit)
switch uint32(ret) {
case E_INVALIDARG:
panic("CoInitializeEx failed with E_INVALIDARG")
case E_OUTOFMEMORY:
panic("CoInitializeEx failed with E_OUTOFMEMORY")
case E_UNEXPECTED:
panic("CoInitializeEx failed with E_UNEXPECTED")
}
return HRESULT(ret)
}
func CoInitialize() {
procCoInitialize.Call(0)
}
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(
uintptr(hGlobal),
uintptr(BoolToBOOL(fDeleteOnRelease)),
uintptr(unsafe.Pointer(&stream)))
switch uint32(ret) {
case E_INVALIDARG:
panic("CreateStreamOnHGlobal failed with E_INVALIDARG")
case E_OUTOFMEMORY:
panic("CreateStreamOnHGlobal failed with E_OUTOFMEMORY")
case E_UNEXPECTED:
panic("CreateStreamOnHGlobal failed with E_UNEXPECTED")
}
return stream
}
func OleInitialise() {
procOleInitialize.Call()
}
func RegisterDragDrop(hwnd HWND, dropTarget *DropTarget) error {
dt := combridge.New[iDropTarget](dropTarget)
hr, _, _ := procRegisterDragDrop.Call(
hwnd,
dt.Ref(),
)
if hr != S_OK {
return syscall.Errno(hr)
}
return nil
}
func RevokeDragDrop(hwnd HWND) error {
hr, _, _ := procRevokeDragDrop.Call(
hwnd,
)
if hr != S_OK {
return syscall.Errno(hr)
}
return nil
}

50
v3/pkg/w32/oleaut32.go Normal file
View file

@ -0,0 +1,50 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modoleaut32 = syscall.NewLazyDLL("oleaut32")
procVariantInit = modoleaut32.NewProc("VariantInit")
procSysAllocString = modoleaut32.NewProc("SysAllocString")
procSysFreeString = modoleaut32.NewProc("SysFreeString")
procSysStringLen = modoleaut32.NewProc("SysStringLen")
procCreateDispTypeInfo = modoleaut32.NewProc("CreateDispTypeInfo")
procCreateStdDispatch = modoleaut32.NewProc("CreateStdDispatch")
)
func VariantInit(v *VARIANT) {
hr, _, _ := procVariantInit.Call(uintptr(unsafe.Pointer(v)))
if hr != 0 {
panic("Invoke VariantInit error.")
}
return
}
func SysAllocString(v string) (ss *int16) {
pss, _, _ := procSysAllocString.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v))))
ss = (*int16)(unsafe.Pointer(pss))
return
}
func SysFreeString(v *int16) {
hr, _, _ := procSysFreeString.Call(uintptr(unsafe.Pointer(v)))
if hr != 0 {
panic("Invoke SysFreeString error.")
}
return
}
func SysStringLen(v *int16) uint {
l, _, _ := procSysStringLen.Call(uintptr(unsafe.Pointer(v)))
return uint(l)
}

96
v3/pkg/w32/popupmenu.go Normal file
View file

@ -0,0 +1,96 @@
//go:build windows
package w32
type Menu HMENU
type PopupMenu Menu
func (m Menu) destroy() bool {
ret, _, _ := procDestroyMenu.Call(uintptr(m))
return ret != 0
}
func (p PopupMenu) destroy() bool {
return Menu(p).destroy()
}
func (p PopupMenu) Track(hwnd HWND, flags uint32, x, y int32) bool {
return TrackPopupMenuEx(
HMENU(p),
flags,
x,
y,
hwnd,
nil)
}
func RemoveMenu(m HMENU, pos, flags int) bool {
ret, _, _ := procRemoveMenu.Call(
uintptr(m),
uintptr(pos),
uintptr(flags))
return ret != 0
}
func (p PopupMenu) Append(flags uint32, id uintptr, text string) bool {
return Menu(p).Append(flags, id, text)
}
func (m Menu) Append(flags uint32, id uintptr, text string) bool {
return AppendMenu(HMENU(m), flags, id, MustStringToUTF16Ptr(text))
}
func (p PopupMenu) Check(id uintptr, checked bool) bool {
return Menu(p).Check(id, checked)
}
func (m Menu) Check(id uintptr, check bool) bool {
var checkState uint = MF_UNCHECKED
if check {
checkState = MF_CHECKED
}
return CheckMenuItem(HMENU(m), id, checkState) != 0
}
func CheckRadio(m HMENU, startID int, endID int, selectedID int) bool {
ret, _, _ := procCheckMenuRadioItem.Call(
m,
uintptr(startID),
uintptr(endID),
uintptr(selectedID),
MF_BYCOMMAND)
return ret != 0
}
func (m Menu) CheckRadio(startID int, endID int, selectedID int) bool {
ret, _, _ := procCheckMenuRadioItem.Call(
uintptr(m),
uintptr(startID),
uintptr(endID),
uintptr(selectedID),
MF_BYCOMMAND)
return ret != 0
}
func CheckMenuItem(menu HMENU, id uintptr, flags uint) uint {
ret, _, _ := procCheckMenuItem.Call(
menu,
id,
uintptr(flags),
)
return uint(ret)
}
func (p PopupMenu) CheckRadio(startID, endID, selectedID int) bool {
return Menu(p).CheckRadio(startID, endID, selectedID)
}
func NewMenu() HMENU {
ret, _, _ := procCreateMenu.Call()
return HMENU(ret)
}
func NewPopupMenu() HMENU {
ret, _, _ := procCreatePopupMenu.Call()
return ret
}

143
v3/pkg/w32/screen.go Normal file
View file

@ -0,0 +1,143 @@
//go:build windows
package w32
import (
"fmt"
"syscall"
"unsafe"
)
type Screen struct {
MONITORINFOEX
HMonitor uintptr
Name string
IsPrimary bool
IsCurrent bool
ScaleFactor float32
Rotation float32
}
type DISPLAY_DEVICE struct {
cb uint32
DeviceName [32]uint16
DeviceString [128]uint16
StateFlags uint32
DeviceID [128]uint16
DeviceKey [128]uint16
}
func getMonitorName(deviceName string) (string, error) {
var device DISPLAY_DEVICE
device.cb = uint32(unsafe.Sizeof(device))
i := uint32(0)
for {
res, _, _ := procEnumDisplayDevices.Call(uintptr(unsafe.Pointer(MustStringToUTF16Ptr(deviceName))), uintptr(i), uintptr(unsafe.Pointer(&device)), 0)
if res == 0 {
break
}
if device.StateFlags&0x1 != 0 {
return syscall.UTF16ToString(device.DeviceString[:]), nil
}
i++
}
return "", fmt.Errorf("monitor name not found for device: %s", deviceName)
}
// I'm not convinced this works properly
func GetRotationForMonitor(displayName [32]uint16) (float32, error) {
var devMode DEVMODE
devMode.DmSize = uint16(unsafe.Sizeof(devMode))
resp, _, _ := procEnumDisplaySettings.Call(uintptr(unsafe.Pointer(&displayName[0])), ENUM_CURRENT_SETTINGS, uintptr(unsafe.Pointer(&devMode)))
if resp == 0 {
return 0, fmt.Errorf("EnumDisplaySettings failed")
}
if (devMode.DmFields & DM_DISPLAYORIENTATION) == 0 {
return 0, fmt.Errorf("DM_DISPLAYORIENTATION not set")
}
switch devMode.DmOrientation {
case DMDO_DEFAULT:
return 0, nil
case DMDO_90:
return 90, nil
case DMDO_180:
return 180, nil
case DMDO_270:
return 270, nil
}
return -1, nil
}
func GetAllScreens() ([]*Screen, error) {
var result []*Screen
var errMessage string
// Get cursor position to determine the current monitor
var cursor POINT
ret, _, _ := procGetCursorPos.Call(uintptr(unsafe.Pointer(&cursor)))
if ret == 0 {
return nil, fmt.Errorf("GetCursorPos failed")
}
// Enumerate the monitors
enumFunc := func(hMonitor uintptr, hdc uintptr, lprcMonitor *RECT, lParam uintptr) uintptr {
monitor := MONITORINFOEX{
MONITORINFO: MONITORINFO{
CbSize: uint32(unsafe.Sizeof(MONITORINFOEX{})),
},
SzDevice: [32]uint16{},
}
ret, _, _ := procGetMonitorInfo.Call(hMonitor, uintptr(unsafe.Pointer(&monitor)))
if ret == 0 {
errMessage = "GetMonitorInfo failed"
return 0 // Stop enumeration
}
screen := &Screen{
MONITORINFOEX: monitor,
HMonitor: hMonitor,
IsPrimary: monitor.DwFlags == MONITORINFOF_PRIMARY,
IsCurrent: rectContainsPoint(monitor.RcMonitor, cursor),
}
// Get monitor name
name, err := getMonitorName(syscall.UTF16ToString(monitor.SzDevice[:]))
if err == nil {
screen.Name = name
}
// Get DPI for monitor
var dpiX, dpiY uint
ret = GetDPIForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
if ret != S_OK {
errMessage = "GetDpiForMonitor failed"
return 0 // Stop enumeration
}
// Convert to scale factor
screen.ScaleFactor = float32(dpiX) / 96.0
// Get rotation of monitor
rot, err := GetRotationForMonitor(monitor.SzDevice)
if err == nil {
screen.Rotation = rot
}
result = append(result, screen)
return 1 // Continue enumeration
}
ret, _, _ = procEnumDisplayMonitors.Call(0, 0, syscall.NewCallback(enumFunc), 0)
if ret == 0 {
return nil, fmt.Errorf("EnumDisplayMonitors failed: %s", errMessage)
}
return result, nil
}
func rectContainsPoint(r RECT, p POINT) bool {
return p.X >= r.Left && p.X < r.Right && p.Y >= r.Top && p.Y < r.Bottom
}

71
v3/pkg/w32/shcore.go Normal file
View file

@ -0,0 +1,71 @@
//go:build windows
package w32
import (
"fmt"
"syscall"
"unsafe"
)
var (
modshcore = syscall.NewLazyDLL("shcore.dll")
procGetDpiForMonitor = modshcore.NewProc("GetDpiForMonitor")
procGetProcessDpiAwareness = modshcore.NewProc("GetProcessDpiAwareness")
procSetProcessDpiAwareness = modshcore.NewProc("SetProcessDpiAwareness")
)
func HasGetProcessDpiAwarenessFunc() bool {
err := procGetProcessDpiAwareness.Find()
return err == nil
}
// GetProcessDpiAwareness retrieves the DPI awareness of the current process.
// Returns one of: PROCESS_DPI_UNAWARE, PROCESS_SYSTEM_DPI_AWARE, or PROCESS_PER_MONITOR_DPI_AWARE.
func GetProcessDpiAwareness() (uint, error) {
var awareness uint
status, _, err := procGetProcessDpiAwareness.Call(0, uintptr(unsafe.Pointer(&awareness)))
if status != S_OK {
return 0, fmt.Errorf("GetProcessDpiAwareness failed: %v", err)
}
return awareness, nil
}
func HasSetProcessDpiAwarenessFunc() bool {
err := procSetProcessDpiAwareness.Find()
return err == nil
}
func SetProcessDpiAwareness(val uint) error {
status, r, err := procSetProcessDpiAwareness.Call(uintptr(val))
if status != S_OK {
return fmt.Errorf("procSetProcessDpiAwareness failed %d: %v %v", status, r, err)
}
return nil
}
func HasGetDPIForMonitorFunc() bool {
err := procGetDpiForMonitor.Find()
return err == nil
}
func GetDPIForMonitor(hmonitor HMONITOR, dpiType MONITOR_DPI_TYPE, dpiX *UINT, dpiY *UINT) uintptr {
ret, _, _ := procGetDpiForMonitor.Call(
hmonitor,
uintptr(dpiType),
uintptr(unsafe.Pointer(dpiX)),
uintptr(unsafe.Pointer(dpiY)))
return ret
}
func GetNotificationFlyoutBounds() (*RECT, error) {
var rect RECT
res, _, err := procSystemParametersInfo.Call(SPI_GETNOTIFYWINDOWRECT, 0, uintptr(unsafe.Pointer(&rect)), 0)
if res == 0 {
_ = err
return nil, err
}
return &rect, nil
}

414
v3/pkg/w32/shell32.go Normal file
View file

@ -0,0 +1,414 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"errors"
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
type CSIDL uint32
const (
CSIDL_DESKTOP = 0x00
CSIDL_INTERNET = 0x01
CSIDL_PROGRAMS = 0x02
CSIDL_CONTROLS = 0x03
CSIDL_PRINTERS = 0x04
CSIDL_PERSONAL = 0x05
CSIDL_FAVORITES = 0x06
CSIDL_STARTUP = 0x07
CSIDL_RECENT = 0x08
CSIDL_SENDTO = 0x09
CSIDL_BITBUCKET = 0x0A
CSIDL_STARTMENU = 0x0B
CSIDL_MYDOCUMENTS = 0x0C
CSIDL_MYMUSIC = 0x0D
CSIDL_MYVIDEO = 0x0E
CSIDL_DESKTOPDIRECTORY = 0x10
CSIDL_DRIVES = 0x11
CSIDL_NETWORK = 0x12
CSIDL_NETHOOD = 0x13
CSIDL_FONTS = 0x14
CSIDL_TEMPLATES = 0x15
CSIDL_COMMON_STARTMENU = 0x16
CSIDL_COMMON_PROGRAMS = 0x17
CSIDL_COMMON_STARTUP = 0x18
CSIDL_COMMON_DESKTOPDIRECTORY = 0x19
CSIDL_APPDATA = 0x1A
CSIDL_PRINTHOOD = 0x1B
CSIDL_LOCAL_APPDATA = 0x1C
CSIDL_ALTSTARTUP = 0x1D
CSIDL_COMMON_ALTSTARTUP = 0x1E
CSIDL_COMMON_FAVORITES = 0x1F
CSIDL_INTERNET_CACHE = 0x20
CSIDL_COOKIES = 0x21
CSIDL_HISTORY = 0x22
CSIDL_COMMON_APPDATA = 0x23
CSIDL_WINDOWS = 0x24
CSIDL_SYSTEM = 0x25
CSIDL_PROGRAM_FILES = 0x26
CSIDL_MYPICTURES = 0x27
CSIDL_PROFILE = 0x28
CSIDL_SYSTEMX86 = 0x29
CSIDL_PROGRAM_FILESX86 = 0x2A
CSIDL_PROGRAM_FILES_COMMON = 0x2B
CSIDL_PROGRAM_FILES_COMMONX86 = 0x2C
CSIDL_COMMON_TEMPLATES = 0x2D
CSIDL_COMMON_DOCUMENTS = 0x2E
CSIDL_COMMON_ADMINTOOLS = 0x2F
CSIDL_ADMINTOOLS = 0x30
CSIDL_CONNECTIONS = 0x31
CSIDL_COMMON_MUSIC = 0x35
CSIDL_COMMON_PICTURES = 0x36
CSIDL_COMMON_VIDEO = 0x37
CSIDL_RESOURCES = 0x38
CSIDL_RESOURCES_LOCALIZED = 0x39
CSIDL_COMMON_OEM_LINKS = 0x3A
CSIDL_CDBURN_AREA = 0x3B
CSIDL_COMPUTERSNEARME = 0x3D
CSIDL_FLAG_CREATE = 0x8000
CSIDL_FLAG_DONT_VERIFY = 0x4000
CSIDL_FLAG_NO_ALIAS = 0x1000
CSIDL_FLAG_PER_USER_INIT = 0x8000
CSIDL_FLAG_MASK = 0xFF00
)
var (
FOLDERID_AccountPictures = NewGUID("{008CA0B1-55B4-4C56-B8A8-4DE4B299D3BE}")
FOLDERID_AddNewPrograms = NewGUID("{DE61D971-5EBC-4F02-A3A9-6C82895E5C04}")
FOLDERID_AdminTools = NewGUID("{724EF170-A42D-4FEF-9F26-B60E846FBA4F}")
FOLDERID_ApplicationShortcuts = NewGUID("{A3918781-E5F2-4890-B3D9-A7E54332328C}")
FOLDERID_AppsFolder = NewGUID("{1E87508D-89C2-42F0-8A7E-645A0F50CA58}")
FOLDERID_AppUpdates = NewGUID("{A305CE99-F527-492B-8B1A-7E76FA98D6E4}")
FOLDERID_CDBurning = NewGUID("{9E52AB10-F80D-49DF-ACB8-4330F5687855}")
FOLDERID_ChangeRemovePrograms = NewGUID("{DF7266AC-9274-4867-8D55-3BD661DE872D}")
FOLDERID_CommonAdminTools = NewGUID("{D0384E7D-BAC3-4797-8F14-CBA229B392B5}")
FOLDERID_CommonOEMLinks = NewGUID("{C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D}")
FOLDERID_CommonPrograms = NewGUID("{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}")
FOLDERID_CommonStartMenu = NewGUID("{A4115719-D62E-491D-AA7C-E74B8BE3B067}")
FOLDERID_CommonStartup = NewGUID("{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}")
FOLDERID_CommonTemplates = NewGUID("{B94237E7-57AC-4347-9151-B08C6C32D1F7}")
FOLDERID_ComputerFolder = NewGUID("{0AC0837C-BBF8-452A-850D-79D08E667CA7}")
FOLDERID_ConflictFolder = NewGUID("{4BFEFB45-347D-4006-A5BE-AC0CB0567192}")
FOLDERID_ConnectionsFolder = NewGUID("{6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD}")
FOLDERID_Contacts = NewGUID("{56784854-C6CB-462B-8169-88E350ACB882}")
FOLDERID_ControlPanelFolder = NewGUID("{82A74AEB-AEB4-465C-A014-D097EE346D63}")
FOLDERID_Cookies = NewGUID("{2B0F765D-C0E9-4171-908E-08A611B84FF6}")
FOLDERID_Desktop = NewGUID("{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}")
FOLDERID_DeviceMetadataStore = NewGUID("{5CE4A5E9-E4EB-479D-B89F-130C02886155}")
FOLDERID_Documents = NewGUID("{FDD39AD0-238F-46AF-ADB4-6C85480369C7}")
FOLDERID_DocumentsLibrary = NewGUID("{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}")
FOLDERID_Downloads = NewGUID("{374DE290-123F-4565-9164-39C4925E467B}")
FOLDERID_Favorites = NewGUID("{1777F761-68AD-4D8A-87BD-30B759FA33DD}")
FOLDERID_Fonts = NewGUID("{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}")
FOLDERID_Games = NewGUID("{CAC52C1A-B53D-4EDC-92D7-6B2E8AC19434}")
FOLDERID_GameTasks = NewGUID("{054FAE61-4DD8-4787-80B6-090220C4B700}")
FOLDERID_History = NewGUID("{D9DC8A3B-B784-432E-A781-5A1130A75963}")
FOLDERID_HomeGroup = NewGUID("{52528A6B-B9E3-4ADD-B60D-588C2DBA842D}")
FOLDERID_HomeGroupCurrentUser = NewGUID("{9B74B6A3-0DFD-4F11-9E78-5F7800F2E772}")
FOLDERID_ImplicitAppShortcuts = NewGUID("{BCB5256F-79F6-4CEE-B725-DC34E402FD46}")
FOLDERID_InternetCache = NewGUID("{352481E8-33BE-4251-BA85-6007CAEDCF9D}")
FOLDERID_InternetFolder = NewGUID("{4D9F7874-4E0C-4904-967B-40B0D20C3E4B}")
FOLDERID_Libraries = NewGUID("{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}")
FOLDERID_Links = NewGUID("{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}")
FOLDERID_LocalAppData = NewGUID("{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}")
FOLDERID_LocalAppDataLow = NewGUID("{A520A1A4-1780-4FF6-BD18-167343C5AF16}")
FOLDERID_LocalizedResourcesDir = NewGUID("{2A00375E-224C-49DE-B8D1-440DF7EF3DDC}")
FOLDERID_Music = NewGUID("{4BD8D571-6D19-48D3-BE97-422220080E43}")
FOLDERID_MusicLibrary = NewGUID("{2112AB0A-C86A-4FFE-A368-0DE96E47012E}")
FOLDERID_NetHood = NewGUID("{C5ABBF53-E17F-4121-8900-86626FC2C973}")
FOLDERID_NetworkFolder = NewGUID("{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}")
FOLDERID_OriginalImages = NewGUID("{2C36C0AA-5812-4B87-BFD0-4CD0DFB19B39}")
FOLDERID_PhotoAlbums = NewGUID("{69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C}")
FOLDERID_Pictures = NewGUID("{33E28130-4E1E-4676-835A-98395C3BC3BB}")
FOLDERID_PicturesLibrary = NewGUID("{A990AE9F-A03B-4E80-94BC-9912D7504104}")
FOLDERID_Playlists = NewGUID("{DE92C1C7-837F-4F69-A3BB-86E631204A23}")
FOLDERID_PrintersFolder = NewGUID("{76FC4E2D-D6AD-4519-A663-37BD56068185}")
FOLDERID_PrintHood = NewGUID("{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}")
FOLDERID_Profile = NewGUID("{5E6C858F-0E22-4760-9AFE-EA3317B67173}")
FOLDERID_ProgramData = NewGUID("{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}")
FOLDERID_ProgramFiles = NewGUID("{905E63B6-C1BF-494E-B29C-65B732D3D21A}")
FOLDERID_ProgramFilesCommon = NewGUID("{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}")
FOLDERID_ProgramFilesCommonX64 = NewGUID("{6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D}")
FOLDERID_ProgramFilesCommonX86 = NewGUID("{DE974D24-D9C6-4D3E-BF91-F4455120B917}")
FOLDERID_ProgramFilesX64 = NewGUID("{6D809377-6AF0-444B-8957-A3773F02200E}")
FOLDERID_ProgramFilesX86 = NewGUID("{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}")
FOLDERID_Programs = NewGUID("{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}")
FOLDERID_Public = NewGUID("{DFDF76A2-C82A-4D63-906A-5644AC457385}")
FOLDERID_PublicDesktop = NewGUID("{C4AA340D-F20F-4863-AFEF-1F769F2BE730}")
FOLDERID_PublicDocuments = NewGUID("{ED4824AF-DCE4-45A8-81E2-FC7965083634}")
FOLDERID_PublicDownloads = NewGUID("{3D644C9B-1FB8-4F30-9B45-F670235F79C0}")
FOLDERID_PublicGameTasks = NewGUID("{DEBF2536-E1A8-4C59-B6A2-414586476AEA}")
FOLDERID_PublicLibraries = NewGUID("{48DAF80B-E6CF-4F4E-B800-0E69D84EE384}")
FOLDERID_PublicMusic = NewGUID("{3214FAB5-9757-4298-BB61-92A9DEAA44FF}")
FOLDERID_PublicPictures = NewGUID("{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}")
FOLDERID_PublicRingtones = NewGUID("{E555AB60-153B-4D17-9F04-A5FE99FC15EC}")
FOLDERID_PublicUserTiles = NewGUID("{0482af6c-08f1-4c34-8c90-e17ec98b1e17}")
FOLDERID_PublicVideos = NewGUID("{2400183A-6185-49FB-A2D8-4A392A602BA3}")
FOLDERID_QuickLaunch = NewGUID("{52a4f021-7b75-48a9-9f6b-4b87a210bc8f}")
FOLDERID_Recent = NewGUID("{AE50C081-EBD2-438A-8655-8A092E34987A}")
FOLDERID_RecordedTVLibrary = NewGUID("{1A6FDBA2-F42D-4358-A798-B74D745926C5}")
FOLDERID_RecycleBinFolder = NewGUID("{B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC}")
FOLDERID_ResourceDir = NewGUID("{8AD10C31-2ADB-4296-A8F7-E4701232C972}")
FOLDERID_Ringtones = NewGUID("{C870044B-F49E-4126-A9C3-B52A1FF411E8}")
FOLDERID_RoamingAppData = NewGUID("{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}")
FOLDERID_RoamingTiles = NewGUID("{AAA8D5A5-F1D6-4259-BAA8-78E7EF60835E}")
FOLDERID_SampleMusic = NewGUID("{B250C668-F57D-4EE1-A63C-290EE7D1AA1F}")
FOLDERID_SamplePictures = NewGUID("{C4900540-2379-4C75-844B-64E6FAF8716B}")
FOLDERID_SamplePlaylists = NewGUID("{15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5}")
FOLDERID_SampleVideos = NewGUID("{859EAD94-2E85-48AD-A71A-0969CB56A6CD}")
FOLDERID_SavedGames = NewGUID("{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}")
FOLDERID_SavedPictures = NewGUID("{3B193882-D3AD-4EAB-965A-69829D1FB59F}")
FOLDERID_SavedPicturesLibrary = NewGUID("{E25B5812-BE88-4BD9-94B0-29233477B6C3}")
FOLDERID_SavedSearches = NewGUID("{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}")
FOLDERID_SEARCH_CSC = NewGUID("{ee32e446-31ca-4aba-814f-a5ebd2fd6d5e}")
FOLDERID_SEARCH_MAPI = NewGUID("{98ec0e18-2098-4d44-8644-66979315a281}")
FOLDERID_SearchHome = NewGUID("{190337d1-b8ca-4121-a639-6d472d16972a}")
FOLDERID_SendTo = NewGUID("{8983036C-27C0-404B-8F08-102D10DCFD74}")
FOLDERID_SidebarDefaultParts = NewGUID("{7B396E54-9EC5-4300-BE0A-2482EBAE1A26}")
FOLDERID_SidebarParts = NewGUID("{A75D362E-50FC-4fb7-AC2C-A8BEAA314493}")
FOLDERID_SkyDrive = NewGUID("{A52BBA46-E9E1-435f-B3D9-28DAA648C0F6}")
FOLDERID_SkyDriveCameraRoll = NewGUID("{767E6811-49CB-4273-87C2-20F355E1085B}")
FOLDERID_SkyDriveDocuments = NewGUID("{24D89E24-2F19-4534-9DDE-6A6671FBB8FE}")
FOLDERID_SkyDriveMusic = NewGUID("{C3F2459E-80D6-45DC-BFEF-1F769F2BE730}")
FOLDERID_SkyDrivePictures = NewGUID("{339719B5-8C47-4894-94C2-D8F77ADD44A6}")
FOLDERID_StartMenu = NewGUID("{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}")
FOLDERID_Startup = NewGUID("{B97D20BB-F46A-4C97-BA10-5E3608430854}")
FOLDERID_SyncManagerFolder = NewGUID("{43668BF8-C14E-49B2-97C9-747784D784B7}")
FOLDERID_SyncResultsFolder = NewGUID("{289a9a43-be44-4057-a41b-587a76d7e7f9}")
FOLDERID_SyncSetupFolder = NewGUID("{0F214138-B1D3-4a90-BBA9-27CBC0C5389A}")
FOLDERID_System = NewGUID("{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}")
FOLDERID_SystemX86 = NewGUID("{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}")
FOLDERID_Templates = NewGUID("{A63293E8-664E-48DB-A079-DF759E0509F7}")
FOLDERID_UserPinned = NewGUID("{9E3995AB-1F9C-4F13-B827-48B24B6C7174}")
FOLDERID_UserProfiles = NewGUID("{0762D272-C50A-4BB0-A382-697DCD729B80}")
FOLDERID_UserProgramFiles = NewGUID("{5CD7AEE2-2219-4A67-B85D-6C9CE15660CB}")
FOLDERID_UserProgramFilesCommon = NewGUID("{BCBD3057-CA5C-4622-B42D-BC56DB0AE516}")
FOLDERID_UsersFiles = NewGUID("{F3CE0F7C-4901-4ACC-8648-D5D44B04EF8F}")
FOLDERID_UsersLibraries = NewGUID("{A302545D-DEFF-464b-ABE8-61C8648D939B}")
FOLDERID_Videos = NewGUID("{18989B1D-99B5-455B-841C-AB7C74E4DDFC}")
FOLDERID_VideosLibrary = NewGUID("{491E922F-5643-4AF4-A7EB-4E7A138D8174}")
FOLDERID_Windows = NewGUID("{F38BF404-1D43-42F2-9305-67DE0B28FC23}")
)
var (
modshell32 = syscall.NewLazyDLL("shell32.dll")
procSHBrowseForFolder = modshell32.NewProc("SHBrowseForFolderW")
procSHGetPathFromIDList = modshell32.NewProc("SHGetPathFromIDListW")
procDragAcceptFiles = modshell32.NewProc("DragAcceptFiles")
procDragQueryFile = modshell32.NewProc("DragQueryFileW")
procDragQueryPoint = modshell32.NewProc("DragQueryPoint")
procDragFinish = modshell32.NewProc("DragFinish")
procShellExecute = modshell32.NewProc("ShellExecuteW")
procExtractIcon = modshell32.NewProc("ExtractIconW")
procGetSpecialFolderPath = modshell32.NewProc("SHGetSpecialFolderPathW")
procShellNotifyIcon = modshell32.NewProc("Shell_NotifyIconW")
procShellNotifyIconGetRect = modshell32.NewProc("Shell_NotifyIconGetRect")
procSHGetKnownFolderPath = modshell32.NewProc("SHGetKnownFolderPath")
procSHAppBarMessage = modshell32.NewProc("SHAppBarMessage")
)
type APPBARDATA struct {
CbSize uint32
HWnd HWND
UCallbackMessage uint32
UEdge uint32
Rc RECT
LParam uintptr
}
func ShellNotifyIcon(cmd uintptr, nid *NOTIFYICONDATA) bool {
ret, _, _ := procShellNotifyIcon.Call(cmd, uintptr(unsafe.Pointer(nid)))
return ret == 1
}
func SHBrowseForFolder(bi *BROWSEINFO) uintptr {
ret, _, _ := procSHBrowseForFolder.Call(uintptr(unsafe.Pointer(bi)))
return ret
}
func SHGetKnownFolderPath(rfid *GUID, dwFlags uint32, hToken HANDLE) (string, error) {
var path *uint16
ret, _, _ := procSHGetKnownFolderPath.Call(uintptr(unsafe.Pointer(rfid)), uintptr(dwFlags), hToken, uintptr(unsafe.Pointer(path)))
if ret != uintptr(windows.S_OK) {
return "", fmt.Errorf("SHGetKnownFolderPath failed: %v", ret)
}
return windows.UTF16PtrToString(path), nil
}
func SHGetPathFromIDList(idl uintptr) string {
buf := make([]uint16, 1024)
procSHGetPathFromIDList.Call(
idl,
uintptr(unsafe.Pointer(&buf[0])))
return syscall.UTF16ToString(buf)
}
func DragAcceptFiles(hwnd HWND, accept bool) {
procDragAcceptFiles.Call(
hwnd,
uintptr(BoolToBOOL(accept)))
}
func DragQueryFile(hDrop HDROP, iFile uint) (fileName string, fileCount uint) {
ret, _, _ := procDragQueryFile.Call(
hDrop,
uintptr(iFile),
0,
0)
fileCount = uint(ret)
if iFile != 0xFFFFFFFF {
buf := make([]uint16, fileCount+1)
ret, _, _ := procDragQueryFile.Call(
hDrop,
uintptr(iFile),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(fileCount+1))
if ret == 0 {
panic("Invoke DragQueryFile error.")
}
fileName = syscall.UTF16ToString(buf)
}
return
}
func DragQueryPoint(hDrop HDROP) (x, y int, isClientArea bool) {
var pt POINT
ret, _, _ := procDragQueryPoint.Call(
uintptr(hDrop),
uintptr(unsafe.Pointer(&pt)))
return int(pt.X), int(pt.Y), (ret == 1)
}
func DragFinish(hDrop HDROP) {
procDragFinish.Call(uintptr(hDrop))
}
func ShellExecute(hwnd HWND, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
var op, param, directory uintptr
if len(lpOperation) != 0 {
op = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
}
if len(lpParameters) != 0 {
param = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters)))
}
if len(lpDirectory) != 0 {
directory = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
}
ret, _, _ := procShellExecute.Call(
uintptr(hwnd),
op,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
param,
directory,
uintptr(nShowCmd))
errorMsg := ""
if ret != 0 && ret <= 32 {
switch int(ret) {
case ERROR_FILE_NOT_FOUND:
errorMsg = "The specified file was not found."
case ERROR_PATH_NOT_FOUND:
errorMsg = "The specified path was not found."
case ERROR_BAD_FORMAT:
errorMsg = "The .exe file is invalid (non-Win32 .exe or error in .exe image)."
case SE_ERR_ACCESSDENIED:
errorMsg = "The operating system denied access to the specified file."
case SE_ERR_ASSOCINCOMPLETE:
errorMsg = "The file name association is incomplete or invalid."
case SE_ERR_DDEBUSY:
errorMsg = "The DDE transaction could not be completed because other DDE transactions were being processed."
case SE_ERR_DDEFAIL:
errorMsg = "The DDE transaction failed."
case SE_ERR_DDETIMEOUT:
errorMsg = "The DDE transaction could not be completed because the request timed out."
case SE_ERR_DLLNOTFOUND:
errorMsg = "The specified DLL was not found."
case SE_ERR_NOASSOC:
errorMsg = "There is no application associated with the given file name extension. This error will also be returned if you attempt to print a file that is not printable."
case SE_ERR_OOM:
errorMsg = "There was not enough memory to complete the operation."
case SE_ERR_SHARE:
errorMsg = "A sharing violation occurred."
default:
errorMsg = fmt.Sprintf("Unknown error occurred with error code %v", ret)
}
} else {
return nil
}
return errors.New(errorMsg)
}
func ExtractIcon(lpszExeFileName string, nIconIndex int) HICON {
ret, _, _ := procExtractIcon.Call(
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpszExeFileName))),
uintptr(nIconIndex))
return HICON(ret)
}
func SHGetSpecialFolderPath(hwndOwner HWND, lpszPath *uint16, csidl CSIDL, fCreate bool) bool {
ret, _, _ := procGetSpecialFolderPath.Call(
uintptr(hwndOwner),
uintptr(unsafe.Pointer(lpszPath)),
uintptr(csidl),
uintptr(BoolToBOOL(fCreate)),
0,
0)
return ret != 0
}
func GetSystrayBounds(hwnd HWND, uid uint32) (*RECT, error) {
var rect RECT
identifier := NOTIFYICONIDENTIFIER{
CbSize: uint32(unsafe.Sizeof(NOTIFYICONIDENTIFIER{})),
HWnd: hwnd,
UId: uid,
}
ret, _, _ := procShellNotifyIconGetRect.Call(
uintptr(unsafe.Pointer(&identifier)),
uintptr(unsafe.Pointer(&rect)))
if ret != S_OK {
return nil, syscall.GetLastError()
}
return &rect, nil
}
// GetTaskbarPosition returns the location of the taskbar.
func GetTaskbarPosition() *APPBARDATA {
var result APPBARDATA
result.CbSize = uint32(unsafe.Sizeof(APPBARDATA{}))
ret, _, _ := procSHAppBarMessage.Call(
ABM_GETTASKBARPOS,
uintptr(unsafe.Pointer(&result)))
if ret == 0 {
return nil
}
return &result
}

26
v3/pkg/w32/shlwapi.go Normal file
View file

@ -0,0 +1,26 @@
//go:build windows
package w32
import (
"syscall"
"unsafe"
)
var (
modshlwapi = syscall.NewLazyDLL("shlwapi.dll")
procSHCreateMemStream = modshlwapi.NewProc("SHCreateMemStream")
)
func SHCreateMemStream(data []byte) (uintptr, error) {
ret, _, err := procSHCreateMemStream.Call(
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
)
if ret == 0 {
return 0, err
}
return ret, nil
}

95
v3/pkg/w32/taskbar.go Normal file
View file

@ -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()
}
}

345
v3/pkg/w32/theme.go Normal file
View file

@ -0,0 +1,345 @@
//go:build windows
package w32
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
type DWMWINDOWATTRIBUTE int32
const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19
const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20
const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34
const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35
const DwmwaTextColor DWMWINDOWATTRIBUTE = 36
const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38
const SPI_GETHIGHCONTRAST = 0x0042
const HCF_HIGHCONTRASTON = 0x00000001
type WINDOWCOMPOSITIONATTRIB DWORD
type HTHEME HANDLE
const (
WCA_UNDEFINED WINDOWCOMPOSITIONATTRIB = 0
WCA_NCRENDERING_ENABLED WINDOWCOMPOSITIONATTRIB = 1
WCA_NCRENDERING_POLICY WINDOWCOMPOSITIONATTRIB = 2
WCA_TRANSITIONS_FORCEDISABLED WINDOWCOMPOSITIONATTRIB = 3
WCA_ALLOW_NCPAINT WINDOWCOMPOSITIONATTRIB = 4
WCA_CAPTION_BUTTON_BOUNDS WINDOWCOMPOSITIONATTRIB = 5
WCA_NONCLIENT_RTL_LAYOUT WINDOWCOMPOSITIONATTRIB = 6
WCA_FORCE_ICONIC_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 7
WCA_EXTENDED_FRAME_BOUNDS WINDOWCOMPOSITIONATTRIB = 8
WCA_HAS_ICONIC_BITMAP WINDOWCOMPOSITIONATTRIB = 9
WCA_THEME_ATTRIBUTES WINDOWCOMPOSITIONATTRIB = 10
WCA_NCRENDERING_EXILED WINDOWCOMPOSITIONATTRIB = 11
WCA_NCADORNMENTINFO WINDOWCOMPOSITIONATTRIB = 12
WCA_EXCLUDED_FROM_LIVEPREVIEW WINDOWCOMPOSITIONATTRIB = 13
WCA_VIDEO_OVERLAY_ACTIVE WINDOWCOMPOSITIONATTRIB = 14
WCA_FORCE_ACTIVEWINDOW_APPEARANCE WINDOWCOMPOSITIONATTRIB = 15
WCA_DISALLOW_PEEK WINDOWCOMPOSITIONATTRIB = 16
WCA_CLOAK WINDOWCOMPOSITIONATTRIB = 17
WCA_CLOAKED WINDOWCOMPOSITIONATTRIB = 18
WCA_ACCENT_POLICY WINDOWCOMPOSITIONATTRIB = 19
WCA_FREEZE_REPRESENTATION WINDOWCOMPOSITIONATTRIB = 20
WCA_EVER_UNCLOAKED WINDOWCOMPOSITIONATTRIB = 21
WCA_VISUAL_OWNER WINDOWCOMPOSITIONATTRIB = 22
WCA_HOLOGRAPHIC WINDOWCOMPOSITIONATTRIB = 23
WCA_EXCLUDED_FROM_DDA WINDOWCOMPOSITIONATTRIB = 24
WCA_PASSIVEUPDATEMODE WINDOWCOMPOSITIONATTRIB = 25
WCA_USEDARKMODECOLORS WINDOWCOMPOSITIONATTRIB = 26
WCA_CORNER_STYLE WINDOWCOMPOSITIONATTRIB = 27
WCA_PART_COLOR WINDOWCOMPOSITIONATTRIB = 28
WCA_DISABLE_MOVESIZE_FEEDBACK WINDOWCOMPOSITIONATTRIB = 29
WCA_LAST WINDOWCOMPOSITIONATTRIB = 30
)
type WINDOWCOMPOSITIONATTRIBDATA struct {
Attrib WINDOWCOMPOSITIONATTRIB
PvData unsafe.Pointer
CbData uintptr
}
var (
uxtheme = syscall.NewLazyDLL("uxtheme.dll")
procSetWindowTheme = uxtheme.NewProc("SetWindowTheme")
procOpenThemeData = uxtheme.NewProc("OpenThemeData")
procCloseThemeData = uxtheme.NewProc("CloseThemeData")
procDrawThemeBackground = uxtheme.NewProc("DrawThemeBackground")
procAllowDarkModeForApplication = uxtheme.NewProc("AllowDarkModeForApp")
procDrawThemeTextEx = uxtheme.NewProc("DrawThemeTextEx")
)
type PreferredAppMode = int32
const (
PreferredAppModeDefault PreferredAppMode = iota
PreferredAppModeAllowDark
PreferredAppModeForceDark
PreferredAppModeForceLight
PreferredAppModeMax
)
var (
AllowDarkModeForWindow func(hwnd HWND, allow bool) uintptr
SetPreferredAppMode func(mode int32) uintptr
FlushMenuThemes func()
RefreshImmersiveColorPolicyState func()
ShouldAppsUseDarkMode func() bool
)
func init() {
if IsWindowsVersionAtLeast(10, 0, 18334) {
// AllowDarkModeForWindow is only available on Windows 10+
localUXTheme, err := windows.LoadLibrary("uxtheme.dll")
if err == nil {
procAllowDarkModeForWindow, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(133))
if err == nil {
AllowDarkModeForWindow = func(hwnd HWND, allow bool) uintptr {
var allowInt int32
if allow {
allowInt = 1
}
ret, _, _ := syscall.SyscallN(procAllowDarkModeForWindow, uintptr(allowInt))
return ret
}
}
// Add ShouldAppsUseDarkMode
procShouldAppsUseDarkMode, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(132))
if err == nil {
ShouldAppsUseDarkMode = func() bool {
ret, _, _ := syscall.SyscallN(procShouldAppsUseDarkMode)
return ret != 0
}
}
// SetPreferredAppMode is only available on Windows 10+
procSetPreferredAppMode, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(135))
if err == nil {
SetPreferredAppMode = func(mode int32) uintptr {
ret, _, _ := syscall.SyscallN(procSetPreferredAppMode, uintptr(mode))
return ret
}
}
// Add FlushMenuThemes
procFlushMenuThemesAddr, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(136))
if err == nil {
FlushMenuThemes = func() {
syscall.SyscallN(procFlushMenuThemesAddr)
}
}
// Add RefreshImmersiveColorPolicyState
procRefreshImmersiveColorPolicyStateAddr, err := windows.GetProcAddressByOrdinal(localUXTheme, uintptr(104))
if err == nil {
RefreshImmersiveColorPolicyState = func() {
syscall.SyscallN(procRefreshImmersiveColorPolicyStateAddr)
}
}
// Initialize dark mode
if SetPreferredAppMode != nil {
SetPreferredAppMode(PreferredAppModeAllowDark)
if RefreshImmersiveColorPolicyState != nil {
RefreshImmersiveColorPolicyState()
}
}
windows.FreeLibrary(localUXTheme)
}
}
}
func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) {
ret, _, err := procDwmSetWindowAttribute.Call(
hwnd,
uintptr(dwAttribute),
uintptr(pvAttribute),
cbAttribute)
if ret != 0 {
_ = err
// println(err.Error())
}
}
func SupportsThemes() bool {
// We can't support Windows versions before 17763
return IsWindowsVersionAtLeast(10, 0, 17763)
}
func SupportsCustomThemes() bool {
return IsWindowsVersionAtLeast(10, 0, 17763)
}
func SupportsBackdropTypes() bool {
return IsWindowsVersionAtLeast(10, 0, 22621)
}
func SupportsImmersiveDarkMode() bool {
return IsWindowsVersionAtLeast(10, 0, 18985)
}
func SetMenuTheme(hwnd uintptr, useDarkMode bool) {
if !SupportsThemes() {
return
}
// Check if dark mode is supported and enabled
if useDarkMode && ShouldAppsUseDarkMode != nil && !ShouldAppsUseDarkMode() {
useDarkMode = false
}
// Set the window theme
themeName := "Explorer"
if useDarkMode {
themeName = "DarkMode_Explorer"
}
procSetWindowTheme.Call(HWND(hwnd), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(themeName))), 0)
// Update the theme state
if RefreshImmersiveColorPolicyState != nil {
RefreshImmersiveColorPolicyState()
}
// Flush menu themes to force a refresh
if FlushMenuThemes != nil {
FlushMenuThemes()
}
// Set dark mode for the window
if AllowDarkModeForWindow != nil {
AllowDarkModeForWindow(HWND(hwnd), useDarkMode)
}
// Force a redraw
InvalidateRect(HWND(hwnd), nil, true)
}
func SetTheme(hwnd uintptr, useDarkMode bool) {
if SupportsThemes() {
attr := DwmwaUseImmersiveDarkModeBefore20h1
if SupportsImmersiveDarkMode() {
attr = DwmwaUseImmersiveDarkMode
}
var winDark int32
if useDarkMode {
winDark = 1
}
dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark))
SetMenuTheme(hwnd, useDarkMode)
}
}
func EnableTranslucency(hwnd uintptr, backdrop uint32) {
dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop))
}
func SetTitleBarColour(hwnd uintptr, titleBarColour uint32) {
// Debug: Print the color value being set
// fmt.Printf("Setting titlebar color to: 0x%08X (RGB: %d, %d, %d)\n", titleBarColour, titleBarColour&0xFF, (titleBarColour>>8)&0xFF, (titleBarColour>>16)&0xFF)
dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour))
}
func SetTitleTextColour(hwnd uintptr, titleTextColour uint32) {
dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour))
}
func SetBorderColour(hwnd uintptr, titleBorderColour uint32) {
dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour))
}
func IsCurrentlyDarkMode() bool {
key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer key.Close()
AppsUseLightTheme, _, err := key.GetIntegerValue("AppsUseLightTheme")
if err != nil {
return false
}
return AppsUseLightTheme == 0
}
type highContrast struct {
CbSize uint32
DwFlags uint32
LpszDefaultScheme *int16
}
func IsCurrentlyHighContrastMode() bool {
var result highContrast
result.CbSize = uint32(unsafe.Sizeof(result))
res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0)
if res == 0 {
_ = err
return false
}
r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON
return r
}
func GetAccentColor() (string, error) {
key, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\DWM`, registry.QUERY_VALUE)
if err != nil {
return "", err
}
defer key.Close()
accentColor, _, err := key.GetIntegerValue("AccentColor")
if err != nil {
return "", err
}
// Extract RGB components from ABGR format (Alpha, Blue, Green, Red)
red := uint8(accentColor & 0xFF)
green := uint8((accentColor >> 8) & 0xFF)
blue := uint8((accentColor >> 16) & 0xFF)
return fmt.Sprintf("rgb(%d,%d,%d)", red, green, blue), nil
}
// OpenThemeData opens theme data for a window and its class
func OpenThemeData(hwnd HWND, pszClassList string) HTHEME {
ret, _, _ := procOpenThemeData.Call(
uintptr(hwnd),
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(pszClassList))))
return HTHEME(ret)
}
// CloseThemeData closes theme data handle
func CloseThemeData(hTheme HTHEME) error {
ret, _, err := procCloseThemeData.Call(uintptr(hTheme))
if ret != 0 {
return err
}
return nil
}
// DrawThemeTextEx draws theme text with extended options
func DrawThemeTextEx(hTheme HTHEME, hdc HDC, iPartId int32, iStateId int32, pszText []uint16, cchText int32, dwTextFlags uint32, pRect *RECT, pOptions *DTTOPTS) error {
ret, _, err := procDrawThemeTextEx.Call(
uintptr(hTheme),
uintptr(hdc),
uintptr(iPartId),
uintptr(iStateId),
uintptr(unsafe.Pointer(&pszText[0])),
uintptr(cchText),
uintptr(dwTextFlags),
uintptr(unsafe.Pointer(pRect)),
uintptr(unsafe.Pointer(pOptions)))
if ret != 0 {
return err
}
return nil
}

19
v3/pkg/w32/timer.go Normal file
View file

@ -0,0 +1,19 @@
//go:build windows
package w32
func SetTimer(hwnd HWND, nIDEvent uintptr, uElapse uint32, lpTimerFunc uintptr) uintptr {
ret, _, _ := procSetTimer.Call(
uintptr(hwnd),
nIDEvent,
uintptr(uElapse),
lpTimerFunc)
return ret
}
func KillTimer(hwnd HWND, nIDEvent uintptr) bool {
ret, _, _ := procKillTimer.Call(
uintptr(hwnd),
nIDEvent)
return ret != 0
}

216
v3/pkg/w32/toolbar.go Normal file
View file

@ -0,0 +1,216 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
// ToolBar messages
const (
TB_ENABLEBUTTON = WM_USER + 1
TB_CHECKBUTTON = WM_USER + 2
TB_PRESSBUTTON = WM_USER + 3
TB_HIDEBUTTON = WM_USER + 4
TB_INDETERMINATE = WM_USER + 5
TB_MARKBUTTON = WM_USER + 6
TB_ISBUTTONENABLED = WM_USER + 9
TB_ISBUTTONCHECKED = WM_USER + 10
TB_ISBUTTONPRESSED = WM_USER + 11
TB_ISBUTTONHIDDEN = WM_USER + 12
TB_ISBUTTONINDETERMINATE = WM_USER + 13
TB_ISBUTTONHIGHLIGHTED = WM_USER + 14
TB_SETSTATE = WM_USER + 17
TB_GETSTATE = WM_USER + 18
TB_ADDBITMAP = WM_USER + 19
TB_DELETEBUTTON = WM_USER + 22
TB_GETBUTTON = WM_USER + 23
TB_BUTTONCOUNT = WM_USER + 24
TB_COMMANDTOINDEX = WM_USER + 25
TB_SAVERESTORE = WM_USER + 76
TB_CUSTOMIZE = WM_USER + 27
TB_ADDSTRING = WM_USER + 77
TB_GETITEMRECT = WM_USER + 29
TB_BUTTONSTRUCTSIZE = WM_USER + 30
TB_SETBUTTONSIZE = WM_USER + 31
TB_SETBITMAPSIZE = WM_USER + 32
TB_AUTOSIZE = WM_USER + 33
TB_GETTOOLTIPS = WM_USER + 35
TB_SETTOOLTIPS = WM_USER + 36
TB_SETPARENT = WM_USER + 37
TB_SETROWS = WM_USER + 39
TB_GETROWS = WM_USER + 40
TB_GETBITMAPFLAGS = WM_USER + 41
TB_SETCMDID = WM_USER + 42
TB_CHANGEBITMAP = WM_USER + 43
TB_GETBITMAP = WM_USER + 44
TB_GETBUTTONTEXT = WM_USER + 75
TB_REPLACEBITMAP = WM_USER + 46
TB_GETBUTTONSIZE = WM_USER + 58
TB_SETBUTTONWIDTH = WM_USER + 59
TB_SETINDENT = WM_USER + 47
TB_SETIMAGELIST = WM_USER + 48
TB_GETIMAGELIST = WM_USER + 49
TB_LOADIMAGES = WM_USER + 50
TB_GETRECT = WM_USER + 51
TB_SETHOTIMAGELIST = WM_USER + 52
TB_GETHOTIMAGELIST = WM_USER + 53
TB_SETDISABLEDIMAGELIST = WM_USER + 54
TB_GETDISABLEDIMAGELIST = WM_USER + 55
TB_SETSTYLE = WM_USER + 56
TB_GETSTYLE = WM_USER + 57
TB_SETMAXTEXTROWS = WM_USER + 60
TB_GETTEXTROWS = WM_USER + 61
TB_GETOBJECT = WM_USER + 62
TB_GETBUTTONINFO = WM_USER + 63
TB_SETBUTTONINFO = WM_USER + 64
TB_INSERTBUTTON = WM_USER + 67
TB_ADDBUTTONS = WM_USER + 68
TB_HITTEST = WM_USER + 69
TB_SETDRAWTEXTFLAGS = WM_USER + 70
TB_GETHOTITEM = WM_USER + 71
TB_SETHOTITEM = WM_USER + 72
TB_SETANCHORHIGHLIGHT = WM_USER + 73
TB_GETANCHORHIGHLIGHT = WM_USER + 74
TB_GETINSERTMARK = WM_USER + 79
TB_SETINSERTMARK = WM_USER + 80
TB_INSERTMARKHITTEST = WM_USER + 81
TB_MOVEBUTTON = WM_USER + 82
TB_GETMAXSIZE = WM_USER + 83
TB_SETEXTENDEDSTYLE = WM_USER + 84
TB_GETEXTENDEDSTYLE = WM_USER + 85
TB_GETPADDING = WM_USER + 86
TB_SETPADDING = WM_USER + 87
TB_SETINSERTMARKCOLOR = WM_USER + 88
TB_GETINSERTMARKCOLOR = WM_USER + 89
TB_MAPACCELERATOR = WM_USER + 90
TB_GETSTRING = WM_USER + 91
TB_SETCOLORSCHEME = CCM_SETCOLORSCHEME
TB_GETCOLORSCHEME = CCM_GETCOLORSCHEME
TB_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT
TB_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT
)
// ToolBar notifications
const (
TBN_FIRST = -700
TBN_DROPDOWN = TBN_FIRST - 10
)
// TBN_DROPDOWN return codes
const (
TBDDRET_DEFAULT = 0
TBDDRET_NODEFAULT = 1
TBDDRET_TREATPRESSED = 2
)
// ToolBar state constants
const (
TBSTATE_CHECKED = 1
TBSTATE_PRESSED = 2
TBSTATE_ENABLED = 4
TBSTATE_HIDDEN = 8
TBSTATE_INDETERMINATE = 16
TBSTATE_WRAP = 32
TBSTATE_ELLIPSES = 0x40
TBSTATE_MARKED = 0x0080
)
// ToolBar style constants
const (
TBSTYLE_BUTTON = 0
TBSTYLE_SEP = 1
TBSTYLE_CHECK = 2
TBSTYLE_GROUP = 4
TBSTYLE_CHECKGROUP = TBSTYLE_GROUP | TBSTYLE_CHECK
TBSTYLE_DROPDOWN = 8
TBSTYLE_AUTOSIZE = 16
TBSTYLE_NOPREFIX = 32
TBSTYLE_TOOLTIPS = 256
TBSTYLE_WRAPABLE = 512
TBSTYLE_ALTDRAG = 1024
TBSTYLE_FLAT = 2048
TBSTYLE_LIST = 4096
TBSTYLE_CUSTOMERASE = 8192
TBSTYLE_REGISTERDROP = 0x4000
TBSTYLE_TRANSPARENT = 0x8000
)
// ToolBar extended style constants
const (
TBSTYLE_EX_DRAWDDARROWS = 0x00000001
TBSTYLE_EX_MIXEDBUTTONS = 8
TBSTYLE_EX_HIDECLIPPEDBUTTONS = 16
TBSTYLE_EX_DOUBLEBUFFER = 0x80
)
// ToolBar button style constants
const (
BTNS_BUTTON = TBSTYLE_BUTTON
BTNS_SEP = TBSTYLE_SEP
BTNS_CHECK = TBSTYLE_CHECK
BTNS_GROUP = TBSTYLE_GROUP
BTNS_CHECKGROUP = TBSTYLE_CHECKGROUP
BTNS_DROPDOWN = TBSTYLE_DROPDOWN
BTNS_AUTOSIZE = TBSTYLE_AUTOSIZE
BTNS_NOPREFIX = TBSTYLE_NOPREFIX
BTNS_WHOLEDROPDOWN = 0x0080
BTNS_SHOWTEXT = 0x0040
)
// TBBUTTONINFO mask flags
const (
TBIF_IMAGE = 0x00000001
TBIF_TEXT = 0x00000002
TBIF_STATE = 0x00000004
TBIF_STYLE = 0x00000008
TBIF_LPARAM = 0x00000010
TBIF_COMMAND = 0x00000020
TBIF_SIZE = 0x00000040
TBIF_BYINDEX = 0x80000000
)
type NMMOUSE struct {
Hdr NMHDR
DwItemSpec uintptr
DwItemData uintptr
Pt POINT
DwHitInfo uintptr
}
type NMTOOLBAR struct {
Hdr NMHDR
IItem int32
TbButton TBBUTTON
CchText int32
PszText *uint16
RcButton RECT
}
type TBBUTTON struct {
IBitmap int32
IdCommand int32
FsState byte
FsStyle byte
//#ifdef _WIN64
// BYTE bReserved[6] // padding for alignment
//#elif defined(_WIN32)
BReserved [2]byte // padding for alignment
//#endif
DwData uintptr
IString uintptr
}
type TBBUTTONINFO struct {
CbSize uint32
DwMask uint32
IdCommand int32
IImage int32
FsState byte
FsStyle byte
Cx uint16
LParam uintptr
PszText uintptr
CchText int32
}

1110
v3/pkg/w32/typedef.go Normal file

File diff suppressed because it is too large Load diff

1510
v3/pkg/w32/user32.go Normal file

File diff suppressed because it is too large Load diff

637
v3/pkg/w32/utils.go Normal file
View file

@ -0,0 +1,637 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"fmt"
"syscall"
"unicode/utf16"
"unsafe"
)
func MustLoadLibrary(name string) uintptr {
lib, err := syscall.LoadLibrary(name)
if err != nil {
panic(err)
}
return uintptr(lib)
}
func MustGetProcAddress(lib uintptr, name string) uintptr {
addr, err := syscall.GetProcAddress(syscall.Handle(lib), name)
if err != nil {
panic(err)
}
return uintptr(addr)
}
func SUCCEEDED(hr HRESULT) bool {
return hr >= 0
}
func FAILED(hr HRESULT) bool {
return hr < 0
}
func LOWORD(dw uint32) uint16 {
return uint16(dw)
}
func HIWORD(dw uint32) uint16 {
return uint16(dw >> 16 & 0xffff)
}
func MAKELONG(lo, hi uint16) uint32 {
return uint32(uint32(lo) | ((uint32(hi)) << 16))
}
func BoolToBOOL(value bool) BOOL {
if value {
return 1
}
return 0
}
func UTF16PtrToString(cstr *uint16) string {
if cstr != nil {
us := make([]uint16, 0, 256)
for p := uintptr(unsafe.Pointer(cstr)); ; p += 2 {
u := *(*uint16)(unsafe.Pointer(p))
if u == 0 {
return string(utf16.Decode(us))
}
us = append(us, u)
}
}
return ""
}
func ComAddRef(unknown *IUnknown) int32 {
ret, _, _ := syscall.SyscallN(uintptr(unknown.Vtbl.AddRef),
uintptr(unsafe.Pointer(unknown)),
0,
0)
return int32(ret)
}
func ComRelease(unknown *IUnknown) int32 {
ret, _, _ := syscall.SyscallN(uintptr(unknown.Vtbl.Release),
uintptr(unsafe.Pointer(unknown)),
0,
0)
return int32(ret)
}
func ComQueryInterface(unknown *IUnknown, id *GUID) *IDispatch {
var disp *IDispatch
hr, _, _ := syscall.SyscallN(uintptr(unknown.Vtbl.QueryInterface),
uintptr(unsafe.Pointer(unknown)),
uintptr(unsafe.Pointer(id)),
uintptr(unsafe.Pointer(&disp)))
if hr != 0 {
panic("Invoke QieryInterface error.")
}
return disp
}
func ComGetIDsOfName(disp *IDispatch, names []string) []int32 {
wnames := make([]*uint16, len(names))
dispid := make([]int32, len(names))
for i := 0; i < len(names); i++ {
wnames[i] = syscall.StringToUTF16Ptr(names[i])
}
hr, _, _ := syscall.SyscallN(disp.lpVtbl.pGetIDsOfNames,
uintptr(unsafe.Pointer(disp)),
uintptr(unsafe.Pointer(IID_NULL)),
uintptr(unsafe.Pointer(&wnames[0])),
uintptr(len(names)),
uintptr(GetUserDefaultLCID()),
uintptr(unsafe.Pointer(&dispid[0])))
if hr != 0 {
panic("Invoke GetIDsOfName error.")
}
return dispid
}
func ComInvoke(disp *IDispatch, dispid int32, dispatch int16, params ...interface{}) (result *VARIANT) {
var dispparams DISPPARAMS
if dispatch&DISPATCH_PROPERTYPUT != 0 {
dispnames := [1]int32{DISPID_PROPERTYPUT}
dispparams.RgdispidNamedArgs = uintptr(unsafe.Pointer(&dispnames[0]))
dispparams.CNamedArgs = 1
}
var vargs []VARIANT
if len(params) > 0 {
vargs = make([]VARIANT, len(params))
for i, v := range params {
//n := len(params)-i-1
n := len(params) - i - 1
VariantInit(&vargs[n])
switch v.(type) {
case bool:
if v.(bool) {
vargs[n] = VARIANT{VT_BOOL, 0, 0, 0, 0xffff}
} else {
vargs[n] = VARIANT{VT_BOOL, 0, 0, 0, 0}
}
case *bool:
vargs[n] = VARIANT{VT_BOOL | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*bool))))}
case byte:
vargs[n] = VARIANT{VT_I1, 0, 0, 0, int64(v.(byte))}
case *byte:
vargs[n] = VARIANT{VT_I1 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*byte))))}
case int16:
vargs[n] = VARIANT{VT_I2, 0, 0, 0, int64(v.(int16))}
case *int16:
vargs[n] = VARIANT{VT_I2 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int16))))}
case uint16:
vargs[n] = VARIANT{VT_UI2, 0, 0, 0, int64(v.(int16))}
case *uint16:
vargs[n] = VARIANT{VT_UI2 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint16))))}
case int, int32:
vargs[n] = VARIANT{VT_UI4, 0, 0, 0, int64(v.(int))}
case *int, *int32:
vargs[n] = VARIANT{VT_I4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int))))}
case uint, uint32:
vargs[n] = VARIANT{VT_UI4, 0, 0, 0, int64(v.(uint))}
case *uint, *uint32:
vargs[n] = VARIANT{VT_UI4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint))))}
case int64:
vargs[n] = VARIANT{VT_I8, 0, 0, 0, v.(int64)}
case *int64:
vargs[n] = VARIANT{VT_I8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int64))))}
case uint64:
vargs[n] = VARIANT{VT_UI8, 0, 0, 0, int64(v.(uint64))}
case *uint64:
vargs[n] = VARIANT{VT_UI8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint64))))}
case float32:
vargs[n] = VARIANT{VT_R4, 0, 0, 0, int64(v.(float32))}
case *float32:
vargs[n] = VARIANT{VT_R4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*float32))))}
case float64:
vargs[n] = VARIANT{VT_R8, 0, 0, 0, int64(v.(float64))}
case *float64:
vargs[n] = VARIANT{VT_R8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*float64))))}
case string:
vargs[n] = VARIANT{VT_BSTR, 0, 0, 0, int64(uintptr(unsafe.Pointer(SysAllocString(v.(string)))))}
case *string:
vargs[n] = VARIANT{VT_BSTR | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*string))))}
case *IDispatch:
vargs[n] = VARIANT{VT_DISPATCH, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*IDispatch))))}
case **IDispatch:
vargs[n] = VARIANT{VT_DISPATCH | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(**IDispatch))))}
case nil:
vargs[n] = VARIANT{VT_NULL, 0, 0, 0, 0}
case *VARIANT:
vargs[n] = VARIANT{VT_VARIANT | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*VARIANT))))}
default:
panic("unknown type")
}
}
dispparams.Rgvarg = uintptr(unsafe.Pointer(&vargs[0]))
dispparams.CArgs = uint32(len(params))
}
var ret VARIANT
var excepInfo EXCEPINFO
VariantInit(&ret)
hr, _, _ := syscall.SyscallN(disp.lpVtbl.pInvoke,
uintptr(unsafe.Pointer(disp)),
uintptr(dispid),
uintptr(unsafe.Pointer(IID_NULL)),
uintptr(GetUserDefaultLCID()),
uintptr(dispatch),
uintptr(unsafe.Pointer(&dispparams)),
uintptr(unsafe.Pointer(&ret)),
uintptr(unsafe.Pointer(&excepInfo)),
0)
if hr != 0 {
if excepInfo.BstrDescription != nil {
bs := UTF16PtrToString(excepInfo.BstrDescription)
panic(bs)
}
}
for _, varg := range vargs {
if varg.VT == VT_BSTR && varg.Val != 0 {
SysFreeString(((*int16)(unsafe.Pointer(uintptr(varg.Val)))))
}
}
result = &ret
return
}
func WMMessageToString(msg uintptr) string {
// Convert windows message to string
switch msg {
case WM_APP:
return "WM_APP"
case WM_ACTIVATE:
return "WM_ACTIVATE"
case WM_ACTIVATEAPP:
return "WM_ACTIVATEAPP"
case WM_AFXFIRST:
return "WM_AFXFIRST"
case WM_AFXLAST:
return "WM_AFXLAST"
case WM_ASKCBFORMATNAME:
return "WM_ASKCBFORMATNAME"
case WM_CANCELJOURNAL:
return "WM_CANCELJOURNAL"
case WM_CANCELMODE:
return "WM_CANCELMODE"
case WM_CAPTURECHANGED:
return "WM_CAPTURECHANGED"
case WM_CHANGECBCHAIN:
return "WM_CHANGECBCHAIN"
case WM_CHAR:
return "WM_CHAR"
case WM_CHARTOITEM:
return "WM_CHARTOITEM"
case WM_CHILDACTIVATE:
return "WM_CHILDACTIVATE"
case WM_CLEAR:
return "WM_CLEAR"
case WM_CLOSE:
return "WM_CLOSE"
case WM_COMMAND:
return "WM_COMMAND"
case WM_COMMNOTIFY /* OBSOLETE */ :
return "WM_COMMNOTIFY"
case WM_COMPACTING:
return "WM_COMPACTING"
case WM_COMPAREITEM:
return "WM_COMPAREITEM"
case WM_CONTEXTMENU:
return "WM_CONTEXTMENU"
case WM_COPY:
return "WM_COPY"
case WM_COPYDATA:
return "WM_COPYDATA"
case WM_CREATE:
return "WM_CREATE"
case WM_CTLCOLORBTN:
return "WM_CTLCOLORBTN"
case WM_CTLCOLORDLG:
return "WM_CTLCOLORDLG"
case WM_CTLCOLOREDIT:
return "WM_CTLCOLOREDIT"
case WM_CTLCOLORLISTBOX:
return "WM_CTLCOLORLISTBOX"
case WM_CTLCOLORMSGBOX:
return "WM_CTLCOLORMSGBOX"
case WM_CTLCOLORSCROLLBAR:
return "WM_CTLCOLORSCROLLBAR"
case WM_CTLCOLORSTATIC:
return "WM_CTLCOLORSTATIC"
case WM_CUT:
return "WM_CUT"
case WM_DEADCHAR:
return "WM_DEADCHAR"
case WM_DELETEITEM:
return "WM_DELETEITEM"
case WM_DESTROY:
return "WM_DESTROY"
case WM_DESTROYCLIPBOARD:
return "WM_DESTROYCLIPBOARD"
case WM_DEVICECHANGE:
return "WM_DEVICECHANGE"
case WM_DEVMODECHANGE:
return "WM_DEVMODECHANGE"
case WM_DISPLAYCHANGE:
return "WM_DISPLAYCHANGE"
case WM_DRAWCLIPBOARD:
return "WM_DRAWCLIPBOARD"
case WM_DRAWITEM:
return "WM_DRAWITEM"
case WM_DROPFILES:
return "WM_DROPFILES"
case WM_ENABLE:
return "WM_ENABLE"
case WM_ENDSESSION:
return "WM_ENDSESSION"
case WM_ENTERIDLE:
return "WM_ENTERIDLE"
case WM_ENTERMENULOOP:
return "WM_ENTERMENULOOP"
case WM_ENTERSIZEMOVE:
return "WM_ENTERSIZEMOVE"
case WM_ERASEBKGND:
return "WM_ERASEBKGND"
case WM_EXITMENULOOP:
return "WM_EXITMENULOOP"
case WM_EXITSIZEMOVE:
return "WM_EXITSIZEMOVE"
case WM_FONTCHANGE:
return "WM_FONTCHANGE"
case WM_GETDLGCODE:
return "WM_GETDLGCODE"
case WM_GETFONT:
return "WM_GETFONT"
case WM_GETHOTKEY:
return "WM_GETHOTKEY"
case WM_GETICON:
return "WM_GETICON"
case WM_GETMINMAXINFO:
return "WM_GETMINMAXINFO"
case WM_GETTEXT:
return "WM_GETTEXT"
case WM_GETTEXTLENGTH:
return "WM_GETTEXTLENGTH"
case WM_HANDHELDFIRST:
return "WM_HANDHELDFIRST"
case WM_HANDHELDLAST:
return "WM_HANDHELDLAST"
case WM_HELP:
return "WM_HELP"
case WM_HOTKEY:
return "WM_HOTKEY"
case WM_HSCROLL:
return "WM_HSCROLL"
case WM_HSCROLLCLIPBOARD:
return "WM_HSCROLLCLIPBOARD"
case WM_ICONERASEBKGND:
return "WM_ICONERASEBKGND"
case WM_INITDIALOG:
return "WM_INITDIALOG"
case WM_INITMENU:
return "WM_INITMENU"
case WM_INITMENUPOPUP:
return "WM_INITMENUPOPUP"
case WM_INPUT:
return "WM_INPUT"
case WM_INPUTLANGCHANGE:
return "WM_INPUTLANGCHANGE"
case WM_INPUTLANGCHANGEREQUEST:
return "WM_INPUTLANGCHANGEREQUEST"
case WM_KEYDOWN:
return "WM_KEYDOWN"
case WM_KEYUP:
return "WM_KEYUP"
case WM_KILLFOCUS:
return "WM_KILLFOCUS"
case WM_MDIACTIVATE:
return "WM_MDIACTIVATE"
case WM_MDICASCADE:
return "WM_MDICASCADE"
case WM_MDICREATE:
return "WM_MDICREATE"
case WM_MDIDESTROY:
return "WM_MDIDESTROY"
case WM_MDIGETACTIVE:
return "WM_MDIGETACTIVE"
case WM_MDIICONARRANGE:
return "WM_MDIICONARRANGE"
case WM_MDIMAXIMIZE:
return "WM_MDIMAXIMIZE"
case WM_MDINEXT:
return "WM_MDINEXT"
case WM_MDIREFRESHMENU:
return "WM_MDIREFRESHMENU"
case WM_MDIRESTORE:
return "WM_MDIRESTORE"
case WM_MDISETMENU:
return "WM_MDISETMENU"
case WM_MDITILE:
return "WM_MDITILE"
case WM_MEASUREITEM:
return "WM_MEASUREITEM"
case WM_GETOBJECT:
return "WM_GETOBJECT"
case WM_CHANGEUISTATE:
return "WM_CHANGEUISTATE"
case WM_UPDATEUISTATE:
return "WM_UPDATEUISTATE"
case WM_QUERYUISTATE:
return "WM_QUERYUISTATE"
case WM_UNINITMENUPOPUP:
return "WM_UNINITMENUPOPUP"
case WM_MENURBUTTONUP:
return "WM_MENURBUTTONUP"
case WM_MENUCOMMAND:
return "WM_MENUCOMMAND"
case WM_MENUGETOBJECT:
return "WM_MENUGETOBJECT"
case WM_MENUDRAG:
return "WM_MENUDRAG"
case WM_APPCOMMAND:
return "WM_APPCOMMAND"
case WM_MENUCHAR:
return "WM_MENUCHAR"
case WM_MENUSELECT:
return "WM_MENUSELECT"
case WM_MOVE:
return "WM_MOVE"
case WM_MOVING:
return "WM_MOVING"
case WM_NCACTIVATE:
return "WM_NCACTIVATE"
case WM_NCCALCSIZE:
return "WM_NCCALCSIZE"
case WM_NCCREATE:
return "WM_NCCREATE"
case WM_NCDESTROY:
return "WM_NCDESTROY"
case WM_NCHITTEST:
return "WM_NCHITTEST"
case WM_NCLBUTTONDBLCLK:
return "WM_NCLBUTTONDBLCLK"
case WM_NCLBUTTONDOWN:
return "WM_NCLBUTTONDOWN"
case WM_NCLBUTTONUP:
return "WM_NCLBUTTONUP"
case WM_NCMBUTTONDBLCLK:
return "WM_NCMBUTTONDBLCLK"
case WM_NCMBUTTONDOWN:
return "WM_NCMBUTTONDOWN"
case WM_NCMBUTTONUP:
return "WM_NCMBUTTONUP"
case WM_NCXBUTTONDOWN:
return "WM_NCXBUTTONDOWN"
case WM_NCXBUTTONUP:
return "WM_NCXBUTTONUP"
case WM_NCXBUTTONDBLCLK:
return "WM_NCXBUTTONDBLCLK"
case WM_NCMOUSEHOVER:
return "WM_NCMOUSEHOVER"
case WM_NCMOUSELEAVE:
return "WM_NCMOUSELEAVE"
case WM_NCMOUSEMOVE:
return "WM_NCMOUSEMOVE"
case WM_NCPAINT:
return "WM_NCPAINT"
case WM_NCRBUTTONDBLCLK:
return "WM_NCRBUTTONDBLCLK"
case WM_NCRBUTTONDOWN:
return "WM_NCRBUTTONDOWN"
case WM_NCRBUTTONUP:
return "WM_NCRBUTTONUP"
case WM_NEXTDLGCTL:
return "WM_NEXTDLGCTL"
case WM_NEXTMENU:
return "WM_NEXTMENU"
case WM_NOTIFY:
return "WM_NOTIFY"
case WM_NOTIFYFORMAT:
return "WM_NOTIFYFORMAT"
case WM_NULL:
return "WM_NULL"
case WM_PAINT:
return "WM_PAINT"
case WM_PAINTCLIPBOARD:
return "WM_PAINTCLIPBOARD"
case WM_PAINTICON:
return "WM_PAINTICON"
case WM_PALETTECHANGED:
return "WM_PALETTECHANGED"
case WM_PALETTEISCHANGING:
return "WM_PALETTEISCHANGING"
case WM_PARENTNOTIFY:
return "WM_PARENTNOTIFY"
case WM_PASTE:
return "WM_PASTE"
case WM_PENWINFIRST:
return "WM_PENWINFIRST"
case WM_PENWINLAST:
return "WM_PENWINLAST"
case WM_POWER:
return "WM_POWER"
case WM_PRINT:
return "WM_PRINT"
case WM_PRINTCLIENT:
return "WM_PRINTCLIENT"
case WM_QUERYDRAGICON:
return "WM_QUERYDRAGICON"
case WM_QUERYENDSESSION:
return "WM_QUERYENDSESSION"
case WM_QUERYNEWPALETTE:
return "WM_QUERYNEWPALETTE"
case WM_QUERYOPEN:
return "WM_QUERYOPEN"
case WM_QUEUESYNC:
return "WM_QUEUESYNC"
case WM_QUIT:
return "WM_QUIT"
case WM_RENDERALLFORMATS:
return "WM_RENDERALLFORMATS"
case WM_RENDERFORMAT:
return "WM_RENDERFORMAT"
case WM_SETCURSOR:
return "WM_SETCURSOR"
case WM_SETFOCUS:
return "WM_SETFOCUS"
case WM_SETFONT:
return "WM_SETFONT"
case WM_SETHOTKEY:
return "WM_SETHOTKEY"
case WM_SETICON:
return "WM_SETICON"
case WM_SETREDRAW:
return "WM_SETREDRAW"
case WM_SETTEXT:
return "WM_SETTEXT"
case WM_SETTINGCHANGE:
return "WM_SETTINGCHANGE"
case WM_SHOWWINDOW:
return "WM_SHOWWINDOW"
case WM_SIZE:
return "WM_SIZE"
case WM_SIZECLIPBOARD:
return "WM_SIZECLIPBOARD"
case WM_SIZING:
return "WM_SIZING"
case WM_SPOOLERSTATUS:
return "WM_SPOOLERSTATUS"
case WM_STYLECHANGED:
return "WM_STYLECHANGED"
case WM_STYLECHANGING:
return "WM_STYLECHANGING"
case WM_SYSCHAR:
return "WM_SYSCHAR"
case WM_SYSCOLORCHANGE:
return "WM_SYSCOLORCHANGE"
case WM_SYSCOMMAND:
return "WM_SYSCOMMAND"
case WM_SYSDEADCHAR:
return "WM_SYSDEADCHAR"
case WM_SYSKEYDOWN:
return "WM_SYSKEYDOWN"
case WM_SYSKEYUP:
return "WM_SYSKEYUP"
case WM_TCARD:
return "WM_TCARD"
case WM_THEMECHANGED:
return "WM_THEMECHANGED"
case WM_TIMECHANGE:
return "WM_TIMECHANGE"
case WM_TIMER:
return "WM_TIMER"
case WM_UNDO:
return "WM_UNDO"
case WM_USER:
return "WM_USER"
case WM_USERCHANGED:
return "WM_USERCHANGED"
case WM_VKEYTOITEM:
return "WM_VKEYTOITEM"
case WM_VSCROLL:
return "WM_VSCROLL"
case WM_VSCROLLCLIPBOARD:
return "WM_VSCROLLCLIPBOARD"
case WM_WINDOWPOSCHANGED:
return "WM_WINDOWPOSCHANGED"
case WM_WINDOWPOSCHANGING:
return "WM_WINDOWPOSCHANGING"
case WM_KEYLAST:
return "WM_KEYLAST"
case WM_SYNCPAINT:
return "WM_SYNCPAINT"
case WM_MOUSEACTIVATE:
return "WM_MOUSEACTIVATE"
case WM_MOUSEMOVE:
return "WM_MOUSEMOVE"
case WM_LBUTTONDOWN:
return "WM_LBUTTONDOWN"
case WM_LBUTTONUP:
return "WM_LBUTTONUP"
case WM_LBUTTONDBLCLK:
return "WM_LBUTTONDBLCLK"
case WM_RBUTTONDOWN:
return "WM_RBUTTONDOWN"
case WM_RBUTTONUP:
return "WM_RBUTTONUP"
case WM_RBUTTONDBLCLK:
return "WM_RBUTTONDBLCLK"
case WM_MBUTTONDOWN:
return "WM_MBUTTONDOWN"
case WM_MBUTTONUP:
return "WM_MBUTTONUP"
case WM_MBUTTONDBLCLK:
return "WM_MBUTTONDBLCLK"
case WM_MOUSEWHEEL:
return "WM_MOUSEWHEEL"
case WM_XBUTTONDOWN:
return "WM_XBUTTONDOWN"
case WM_XBUTTONUP:
return "WM_XBUTTONUP"
case WM_MOUSELAST:
return "WM_MOUSELAST"
case WM_MOUSEHOVER:
return "WM_MOUSEHOVER"
case WM_MOUSELEAVE:
return "WM_MOUSELEAVE"
case WM_CLIPBOARDUPDATE:
return "WM_CLIPBOARDUPDATE"
default:
return fmt.Sprintf("0x%08x", msg)
}
}

16
v3/pkg/w32/vars.go Normal file
View file

@ -0,0 +1,16 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
var (
IID_NULL = &GUID{0x00000000, 0x0000, 0x0000, [8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}
IID_IUnknown = &GUID{0x00000000, 0x0000, 0x0000, [8]byte{0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}
IID_IDispatch = &GUID{0x00020400, 0x0000, 0x0000, [8]byte{0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}
IID_IConnectionPointContainer = &GUID{0xB196B284, 0xBAB4, 0x101A, [8]byte{0xB6, 0x9C, 0x00, 0xAA, 0x00, 0x34, 0x1D, 0x07}}
IID_IConnectionPoint = &GUID{0xB196B286, 0xBAB4, 0x101A, [8]byte{0xB6, 0x9C, 0x00, 0xAA, 0x00, 0x34, 0x1D, 0x07}}
)

21
v3/pkg/w32/wda.go Normal file
View file

@ -0,0 +1,21 @@
//go:build windows
package w32
const (
WDA_NONE = 0x00000000
WDA_MONITOR = 0x00000001
WDA_EXCLUDEFROMCAPTURE = 0x00000011 // windows 10 2004+
)
func SetWindowDisplayAffinity(hwnd uintptr, affinity uint32) bool {
if affinity == WDA_EXCLUDEFROMCAPTURE && !IsWindowsVersionAtLeast(10, 0, 19041) {
// for older windows versions, use WDA_MONITOR
affinity = WDA_MONITOR
}
ret, _, _ := procSetWindowDisplayAffinity.Call(
hwnd,
uintptr(affinity),
)
return ret != 0
}

377
v3/pkg/w32/window.go Normal file
View file

@ -0,0 +1,377 @@
//go:build windows
package w32
import (
"fmt"
"strconv"
"strings"
"sync"
"syscall"
"unsafe"
)
var (
user32 = syscall.NewLazyDLL("user32.dll")
getSystemMenu = user32.NewProc("GetSystemMenu")
getMenuProc = user32.NewProc("GetMenu")
enableMenuItem = user32.NewProc("EnableMenuItem")
findWindow = user32.NewProc("FindWindowW")
sendMessage = user32.NewProc("SendMessageW")
vkKeyScan = user32.NewProc("VkKeyScanW") // Use W version for Unicode
)
func VkKeyScan(ch uint16) uint16 {
ret, _, _ := syscall.SyscallN(
vkKeyScan.Addr(),
uintptr(ch),
)
return uint16(ret)
}
const (
WMCOPYDATA_SINGLE_INSTANCE_DATA = 1542
)
type COPYDATASTRUCT struct {
DwData uintptr
CbData uint32
LpData uintptr
}
var Fatal func(error)
const (
GCLP_HBRBACKGROUND int32 = -10
GCLP_HICON int32 = -14
)
type WINDOWPOS struct {
HwndInsertAfter HWND
X int32
Y int32
Cx int32
Cy int32
Flags uint32
}
func ExtendFrameIntoClientArea(hwnd uintptr, extend bool) error {
// -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11)
// Also shows the caption buttons if transparent ant translucent but they don't work.
// 0: Adds the default frame styling but no aero shadow, does not show the caption buttons.
// 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons
// are shown if transparent ant translucent.
var margins MARGINS
if extend {
margins = MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons
}
if err := dwmExtendFrameIntoClientArea(hwnd, &margins); err != nil {
return fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err)
}
return nil
}
func IsVisible(hwnd uintptr) bool {
ret, _, _ := procIsWindowVisible.Call(hwnd)
return ret != 0
}
func IsWindowFullScreen(hwnd uintptr) bool {
wRect := GetWindowRect(hwnd)
m := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY)
var mi MONITORINFO
mi.CbSize = uint32(unsafe.Sizeof(mi))
if !GetMonitorInfo(m, &mi) {
return false
}
return wRect.Left == mi.RcMonitor.Left &&
wRect.Top == mi.RcMonitor.Top &&
wRect.Right == mi.RcMonitor.Right &&
wRect.Bottom == mi.RcMonitor.Bottom
}
func IsWindowMaximised(hwnd uintptr) bool {
style := uint32(getWindowLong(hwnd, GWL_STYLE))
return style&WS_MAXIMIZE != 0
}
func IsWindowMinimised(hwnd uintptr) bool {
style := uint32(getWindowLong(hwnd, GWL_STYLE))
return style&WS_MINIMIZE != 0
}
func RestoreWindow(hwnd uintptr) {
showWindow(hwnd, SW_RESTORE)
}
func ShowWindowMaximised(hwnd uintptr) {
showWindow(hwnd, SW_MAXIMIZE)
}
func ShowWindowMinimised(hwnd uintptr) {
showWindow(hwnd, SW_MINIMIZE)
}
func SetApplicationIcon(hwnd uintptr, icon HICON) {
setClassLongPtr(hwnd, GCLP_HICON, icon)
}
func SetBackgroundColour(hwnd uintptr, r, g, b uint8) {
col := uint32(r) | uint32(g)<<8 | uint32(b)<<16
hbrush, _, _ := procCreateSolidBrush.Call(uintptr(col))
setClassLongPtr(hwnd, GCLP_HBRBACKGROUND, hbrush)
}
func IsWindowNormal(hwnd uintptr) bool {
return !IsWindowMaximised(hwnd) && !IsWindowMinimised(hwnd) && !IsWindowFullScreen(hwnd)
}
func setClassLongPtr(hwnd uintptr, param int32, val uintptr) bool {
proc := procSetClassLongPtr
if strconv.IntSize == 32 {
/*
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongptrw
Note: To write code that is compatible with both 32-bit and 64-bit Windows, use SetClassLongPtr.
When compiling for 32-bit Windows, SetClassLongPtr is defined as a call to the SetClassLong function
=> We have to do this dynamically when directly calling the DLL procedures
*/
proc = procSetClassLong
}
ret, _, _ := proc.Call(
hwnd,
uintptr(param),
val,
)
return ret != 0
}
func getWindowLong(hwnd uintptr, index int) int32 {
ret, _, _ := procGetWindowLong.Call(
hwnd,
uintptr(index))
return int32(ret)
}
func showWindow(hwnd uintptr, cmdshow int) bool {
ret, _, _ := procShowWindow.Call(
hwnd,
uintptr(cmdshow))
return ret != 0
}
func stripNulls(str string) string {
// Split the string into substrings at each null character
substrings := strings.Split(str, "\x00")
// Join the substrings back into a single string
strippedStr := strings.Join(substrings, "")
return strippedStr
}
func MustStringToUTF16Ptr(input string) *uint16 {
input = stripNulls(input)
result, err := syscall.UTF16PtrFromString(input)
if err != nil {
Fatal(err)
}
return result
}
// MustStringToUTF16uintptr converts input to a NUL-terminated UTF-16 buffer and returns its pointer as a uintptr.
// It first removes any internal NUL characters from input, then converts the result to a UTF-16 pointer.
// The function panics if the conversion fails.
func MustStringToUTF16uintptr(input string) uintptr {
input = stripNulls(input)
ret, err := syscall.UTF16PtrFromString(input)
if err != nil {
panic(err)
}
return uintptr(unsafe.Pointer(ret))
}
// MustStringToUTF16 converts s to UTF-16 encoding, stripping any embedded NULs and panicking on error.
//
// The returned slice is suitable for Windows API calls that expect a UTF-16 encoded string.
func MustStringToUTF16(input string) []uint16 {
input = stripNulls(input)
ret, err := syscall.UTF16FromString(input)
if err != nil {
panic(err)
}
return ret
}
// StringToUTF16 converts input to a UTF-16 encoded, NUL-terminated []uint16 suitable for Windows API calls.
// It first removes any embedded NUL ('\x00') characters from input. The returned slice is NUL-terminated;
// an error is returned if the conversion fails.
func StringToUTF16(input string) ([]uint16, error) {
input = stripNulls(input)
return syscall.UTF16FromString(input)
}
func CenterWindow(hwnd HWND) {
windowInfo := getWindowInfo(hwnd)
frameless := windowInfo.IsPopup()
info := GetMonitorInfoForWindow(hwnd)
workRect := info.RcWork
screenMiddleW := workRect.Left + (workRect.Right-workRect.Left)/2
screenMiddleH := workRect.Top + (workRect.Bottom-workRect.Top)/2
var winRect *RECT
if !frameless {
winRect = GetWindowRect(hwnd)
} else {
winRect = GetClientRect(hwnd)
}
winWidth := winRect.Right - winRect.Left
winHeight := winRect.Bottom - winRect.Top
windowX := screenMiddleW - (winWidth / 2)
windowY := screenMiddleH - (winHeight / 2)
SetWindowPos(hwnd, HWND_TOP, int(windowX), int(windowY), int(winWidth), int(winHeight), SWP_NOSIZE)
}
func getWindowInfo(hwnd HWND) *WINDOWINFO {
var info WINDOWINFO
info.CbSize = uint32(unsafe.Sizeof(info))
GetWindowInfo(hwnd, &info)
return &info
}
func GetMonitorInfoForWindow(hwnd HWND) *MONITORINFO {
currentMonitor := MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)
var info MONITORINFO
info.CbSize = uint32(unsafe.Sizeof(info))
GetMonitorInfo(currentMonitor, &info)
return &info
}
type WindowProc func(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr
var windowClasses = make(map[string]HINSTANCE)
var windowClassesLock sync.Mutex
func getWindowClass(name string) (HINSTANCE, bool) {
windowClassesLock.Lock()
defer windowClassesLock.Unlock()
result, exists := windowClasses[name]
return result, exists
}
func setWindowClass(name string, instance HINSTANCE) {
windowClassesLock.Lock()
defer windowClassesLock.Unlock()
windowClasses[name] = instance
}
func RegisterWindow(name string, proc WindowProc) (HINSTANCE, error) {
classInstance, exists := getWindowClass(name)
if exists {
return classInstance, nil
}
applicationInstance := GetModuleHandle("")
if applicationInstance == 0 {
return 0, fmt.Errorf("get module handle failed")
}
var wc WNDCLASSEX
wc.Size = uint32(unsafe.Sizeof(wc))
wc.WndProc = syscall.NewCallback(proc)
wc.Instance = applicationInstance
wc.Icon = LoadIconWithResourceID(0, uint16(IDI_APPLICATION))
wc.Cursor = LoadCursorWithResourceID(0, uint16(IDC_ARROW))
wc.Background = COLOR_BTNFACE + 1
wc.ClassName = MustStringToUTF16Ptr(name)
atom := RegisterClassEx(&wc)
if atom == 0 {
panic(syscall.GetLastError())
}
setWindowClass(name, applicationInstance)
return applicationInstance, nil
}
func FlashWindow(hwnd HWND, enabled bool) {
var flashInfo FLASHWINFO
flashInfo.CbSize = uint32(unsafe.Sizeof(flashInfo))
flashInfo.Hwnd = hwnd
if enabled {
flashInfo.DwFlags = FLASHW_ALL | FLASHW_TIMERNOFG
} else {
flashInfo.DwFlags = FLASHW_STOP
}
_, _, _ = procFlashWindowEx.Call(uintptr(unsafe.Pointer(&flashInfo)))
}
func EnumChildWindows(hwnd HWND, callback func(hwnd HWND, lparam LPARAM) LRESULT) LRESULT {
r, _, _ := procEnumChildWindows.Call(hwnd, syscall.NewCallback(callback), 0)
return r
}
func DisableCloseButton(hwnd HWND) error {
hSysMenu, _, err := getSystemMenu.Call(hwnd, 0)
if hSysMenu == 0 {
return err
}
r1, _, err := enableMenuItem.Call(hSysMenu, SC_CLOSE, MF_BYCOMMAND|MF_DISABLED|MF_GRAYED)
if r1 == 0 {
return err
}
return nil
}
func EnableCloseButton(hwnd HWND) error {
hSysMenu, _, err := getSystemMenu.Call(hwnd, 0)
if hSysMenu == 0 {
return err
}
r1, _, err := enableMenuItem.Call(hSysMenu, SC_CLOSE, MF_BYCOMMAND|MF_ENABLED)
if r1 == 0 {
return err
}
return nil
}
func FindWindowW(className, windowName *uint16) HWND {
ret, _, _ := findWindow.Call(
uintptr(unsafe.Pointer(className)),
uintptr(unsafe.Pointer(windowName)),
)
return HWND(ret)
}
func SendMessageToWindow(hwnd HWND, msg string) {
// Convert data to UTF16 string
dataUTF16, err := StringToUTF16(msg)
if err != nil {
return
}
// Prepare COPYDATASTRUCT
cds := COPYDATASTRUCT{
DwData: WMCOPYDATA_SINGLE_INSTANCE_DATA,
CbData: uint32((len(dataUTF16) * 2) + 1), // +1 for null terminator
LpData: uintptr(unsafe.Pointer(&dataUTF16[0])),
}
// Send message to first instance
_, _, _ = procSendMessage.Call(
hwnd,
WM_COPYDATA,
0,
uintptr(unsafe.Pointer(&cds)),
)
}
// GetMenu retrieves a handle to the menu assigned to the specified window
func GetMenu(hwnd HWND) HMENU {
ret, _, _ := getMenuProc.Call(hwnd)
return ret
}