diff --git a/README.md b/README.md index 6f7fd93..cb76318 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,7 @@ Implemented dialogs: Behavior on Windows, macOS and other Unixes might differ slightly. Some of that is intended (reflecting platform differences), -other bits are unfortunate limitations, -others still are open to be fixed. +other bits are unfortunate limitations. ## Why? @@ -37,7 +36,8 @@ Why reinvent this particular wheel? * Explorer shell not required * works in Server Core * Unicode support - * High DPI support (no manifest required) + * High DPI (no manifest required) + * Visual Styles (no manifest required) * WSL/Cygwin/MSYS2 [support](https://github.com/ncruces/zenity/wiki/Zenity-for-WSL,-Cygwin,-MSYS2) * on macOS: * only dependency is `osascript` diff --git a/color_windows.go b/color_windows.go index ce9dcd3..a678f4a 100644 --- a/color_windows.go +++ b/color_windows.go @@ -9,7 +9,7 @@ import ( var ( chooseColor = comdlg32.NewProc("ChooseColorW") - savedColors = [16]uint32{} + savedColors [16]uint32 colorsMutex sync.Mutex ) diff --git a/file_windows.go b/file_windows.go index 8727206..e5a9902 100644 --- a/file_windows.go +++ b/file_windows.go @@ -37,7 +37,7 @@ func selectFile(opts options) (string, error) { args.Filter = &initFilters(opts.fileFilters)[0] } - res := [32768]uint16{} + var res [32768]uint16 args.File = &res[0] args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) @@ -82,7 +82,7 @@ func selectFileMutiple(opts options) ([]string, error) { args.Filter = &initFilters(opts.fileFilters)[0] } - res := [32768 + 1024*256]uint16{} + var res [32768 + 1024*256]uint16 args.File = &res[0] args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) @@ -158,7 +158,7 @@ func selectFileSave(opts options) (string, error) { args.Filter = &initFilters(opts.fileFilters)[0] } - res := [32768]uint16{} + var res [32768]uint16 args.File = &res[0] args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) @@ -339,7 +339,7 @@ func browseForFolder(opts options) (string, []string, error) { } defer coTaskMemFree.Call(ptr) - res := [32768]uint16{} + var res [32768]uint16 shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0) str := syscall.UTF16ToString(res[:]) diff --git a/msg_windows.go b/msg_windows.go index 0f4214e..ede68f4 100644 --- a/msg_windows.go +++ b/msg_windows.go @@ -80,7 +80,7 @@ func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFun return hookDialog(opts.ctx, func(wnd uintptr) { enumChildWindows.Call(wnd, syscall.NewCallback(func(wnd, lparam uintptr) uintptr { - name := [8]uint16{} + var name [8]uint16 getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) if syscall.UTF16ToString(name[:]) == "Button" { ctl, _, _ := getDlgCtrlID.Call(wnd) diff --git a/progress_windows.go b/progress_windows.go index 30213f6..16830c6 100644 --- a/progress_windows.go +++ b/progress_windows.go @@ -129,7 +129,7 @@ func progress(opts options) (ProgressDialog, error) { centerWindow(wnd) showWindow.Call(wnd, 1 /* SW_SHOWNORMAL */, 0) if opts.maxValue < 0 { - sendMessage.Call(progCtl, 0x410 /* PBM_SETMARQUEE */, 1, 0) + sendMessage.Call(progCtl, 0x40a /* PBM_SETMARQUEE */, 1, 0) } else { sendMessage.Call(progCtl, 0x402 /* PBM_SETPOS */, 33, 0) sendMessage.Call(progCtl, 0x406 /* PBM_SETRANGE32 */, 0, uintptr(opts.maxValue)) diff --git a/util_windows.go b/util_windows.go index 076f164..1b5bf6f 100644 --- a/util_windows.go +++ b/util_windows.go @@ -32,6 +32,10 @@ var ( getModuleHandle = kernel32.NewProc("GetModuleHandleW") getCurrentThreadId = kernel32.NewProc("GetCurrentThreadId") getConsoleWindow = kernel32.NewProc("GetConsoleWindow") + getSystemDirectory = kernel32.NewProc("GetSystemDirectoryW") + createActCtx = kernel32.NewProc("CreateActCtxW") + activateActCtx = kernel32.NewProc("ActivateActCtx") + deactivateActCtx = kernel32.NewProc("DeactivateActCtx") coInitializeEx = ole32.NewProc("CoInitializeEx") coUninitialize = ole32.NewProc("CoUninitialize") @@ -98,15 +102,17 @@ func setup() context.CancelFunc { setForegroundWindow.Call(hwnd) } - var old uintptr runtime.LockOSThread() + + var restore uintptr + cookie := enableVisualStyles() if setThreadDpiAwarenessContext.Find() == nil { // try: // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE // DPI_AWARENESS_CONTEXT_SYSTEM_AWARE for i := -4; i <= -2; i++ { - restore, _, _ := setThreadDpiAwarenessContext.Call(uintptr(i)) + restore, _, _ = setThreadDpiAwarenessContext.Call(uintptr(i)) if restore != 0 { break } @@ -118,8 +124,11 @@ func setup() context.CancelFunc { icc.ICC = 0x00004020 // ICC_STANDARD_CLASSES|ICC_PROGRESS_CLASS return func() { - if old != 0 { - setThreadDpiAwarenessContext.Call(old) + if restore != 0 { + setThreadDpiAwarenessContext.Call(restore) + } + if cookie != 0 { + deactivateActCtx.Call(cookie) } runtime.UnlockOSThread() } @@ -145,7 +154,7 @@ func hookDialog(ctx context.Context, initDialog func(wnd uintptr)) (unhook conte hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET syscall.NewCallback(func(code int32, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { if lparam.Message == 0x0110 { // WM_INITDIALOG - name := [8]uint16{} + var name [8]uint16 getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box var close bool @@ -290,26 +299,6 @@ func registerClass(instance, proc uintptr) (uintptr, error) { return ret, err } -// https://stackoverflow.com/questions/4308503/how-to-enable-visual-styles-without-a-manifest -// ULONG_PTR EnableVisualStyles(VOID) -// { -// TCHAR dir[MAX_PATH]; -// ULONG_PTR ulpActivationCookie = FALSE; -// ACTCTX actCtx = -// { -// sizeof(actCtx), -// ACTCTX_FLAG_RESOURCE_NAME_VALID -// | ACTCTX_FLAG_SET_PROCESS_DEFAULT -// | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, -// TEXT("shell32.dll"), 0, 0, dir, (LPCTSTR)124 -// }; -// UINT cch = GetSystemDirectory(dir, sizeof(dir) / sizeof(*dir)); -// if (cch >= sizeof(dir) / sizeof(*dir)) { return FALSE; /*shouldn't happen*/ } -// dir[cch] = TEXT('\0'); -// ActivateActCtx(CreateActCtx(&actCtx), &ulpActivationCookie); -// return ulpActivationCookie; -// } - // https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues func messageLoop(wnd uintptr) error { getMessage := getMessage.Addr() @@ -335,6 +324,46 @@ func messageLoop(wnd uintptr) error { } } +// https://stackoverflow.com/questions/4308503/how-to-enable-visual-styles-without-a-manifest +func enableVisualStyles() (cookie uintptr) { + var dir [260]uint16 + n, _, _ := getSystemDirectory.Call(uintptr(unsafe.Pointer(&dir[0])), uintptr(len(dir))) + if n == 0 || int(n) >= len(dir) { + return + } + + var ctx _ACTCTX + ctx.Size = uint32(unsafe.Sizeof(ctx)) + ctx.Flags = 0x01c // ACTCTX_FLAG_RESOURCE_NAME_VALID|ACTCTX_FLAG_SET_PROCESS_DEFAULT|ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID + ctx.Source = syscall.StringToUTF16Ptr("shell32.dll") + ctx.AssemblyDirectory = &dir[0] + ctx.ResourceName = 124 + + if h, _, _ := createActCtx.Call(uintptr(unsafe.Pointer(&ctx))); h != 0 { + activateActCtx.Call(h, uintptr(unsafe.Pointer(&cookie))) + } + return +} + +// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-actctxw +type _ACTCTX struct { + Size uint32 + Flags uint32 + Source *uint16 + ProcessorArchitecture uint16 + LangId uint16 + AssemblyDirectory *uint16 + ResourceName uintptr + ApplicationName *uint16 + Module uintptr +} + +// https://docs.microsoft.com/en-us/windows/win32/api/commctrl/ns-commctrl-initcommoncontrolsex +type _INITCOMMONCONTROLSEX struct { + Size uint32 + ICC uint32 +} + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-cwpretstruct type _CWPRETSTRUCT struct { Result uintptr @@ -420,12 +449,6 @@ type _WNDCLASSEX struct { IconSm uintptr } -// https://docs.microsoft.com/en-us/windows/win32/api/commctrl/ns-commctrl-initcommoncontrolsex -type _INITCOMMONCONTROLSEX struct { - Size uint32 - ICC uint32 -} - // https://github.com/wine-mirror/wine/blob/master/include/unknwn.idl type _IUnknownVtbl struct {