WIP: progress (windows).

This commit is contained in:
Nuno Cruces 2021-04-28 01:27:28 +01:00
parent de7c119f33
commit 7b98716a20
6 changed files with 64 additions and 41 deletions

View file

@ -19,8 +19,7 @@ Implemented dialogs:
Behavior on Windows, macOS and other Unixes might differ slightly. Behavior on Windows, macOS and other Unixes might differ slightly.
Some of that is intended (reflecting platform differences), Some of that is intended (reflecting platform differences),
other bits are unfortunate limitations, other bits are unfortunate limitations.
others still are open to be fixed.
## Why? ## Why?
@ -37,7 +36,8 @@ Why reinvent this particular wheel?
* Explorer shell not required * Explorer shell not required
* works in Server Core * works in Server Core
* Unicode support * 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) * WSL/Cygwin/MSYS2 [support](https://github.com/ncruces/zenity/wiki/Zenity-for-WSL,-Cygwin,-MSYS2)
* on macOS: * on macOS:
* only dependency is `osascript` * only dependency is `osascript`

View file

@ -9,7 +9,7 @@ import (
var ( var (
chooseColor = comdlg32.NewProc("ChooseColorW") chooseColor = comdlg32.NewProc("ChooseColorW")
savedColors = [16]uint32{} savedColors [16]uint32
colorsMutex sync.Mutex colorsMutex sync.Mutex
) )

View file

@ -37,7 +37,7 @@ func selectFile(opts options) (string, error) {
args.Filter = &initFilters(opts.fileFilters)[0] args.Filter = &initFilters(opts.fileFilters)[0]
} }
res := [32768]uint16{} var res [32768]uint16
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, 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] args.Filter = &initFilters(opts.fileFilters)[0]
} }
res := [32768 + 1024*256]uint16{} var res [32768 + 1024*256]uint16
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, 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] args.Filter = &initFilters(opts.fileFilters)[0]
} }
res := [32768]uint16{} var res [32768]uint16
args.File = &res[0] args.File = &res[0]
args.MaxFile = uint32(len(res)) args.MaxFile = uint32(len(res))
args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:])
@ -339,7 +339,7 @@ func browseForFolder(opts options) (string, []string, error) {
} }
defer coTaskMemFree.Call(ptr) defer coTaskMemFree.Call(ptr)
res := [32768]uint16{} var res [32768]uint16
shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0) shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0)
str := syscall.UTF16ToString(res[:]) str := syscall.UTF16ToString(res[:])

View file

@ -80,7 +80,7 @@ func hookMessageLabels(kind messageKind, opts options) (unhook context.CancelFun
return hookDialog(opts.ctx, func(wnd uintptr) { return hookDialog(opts.ctx, func(wnd uintptr) {
enumChildWindows.Call(wnd, enumChildWindows.Call(wnd,
syscall.NewCallback(func(wnd, lparam uintptr) uintptr { syscall.NewCallback(func(wnd, lparam uintptr) uintptr {
name := [8]uint16{} var name [8]uint16
getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) getClassName.Call(wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
if syscall.UTF16ToString(name[:]) == "Button" { if syscall.UTF16ToString(name[:]) == "Button" {
ctl, _, _ := getDlgCtrlID.Call(wnd) ctl, _, _ := getDlgCtrlID.Call(wnd)

View file

@ -129,7 +129,7 @@ func progress(opts options) (ProgressDialog, error) {
centerWindow(wnd) centerWindow(wnd)
showWindow.Call(wnd, 1 /* SW_SHOWNORMAL */, 0) showWindow.Call(wnd, 1 /* SW_SHOWNORMAL */, 0)
if opts.maxValue < 0 { if opts.maxValue < 0 {
sendMessage.Call(progCtl, 0x410 /* PBM_SETMARQUEE */, 1, 0) sendMessage.Call(progCtl, 0x40a /* PBM_SETMARQUEE */, 1, 0)
} else { } else {
sendMessage.Call(progCtl, 0x402 /* PBM_SETPOS */, 33, 0) sendMessage.Call(progCtl, 0x402 /* PBM_SETPOS */, 33, 0)
sendMessage.Call(progCtl, 0x406 /* PBM_SETRANGE32 */, 0, uintptr(opts.maxValue)) sendMessage.Call(progCtl, 0x406 /* PBM_SETRANGE32 */, 0, uintptr(opts.maxValue))

View file

@ -32,6 +32,10 @@ var (
getModuleHandle = kernel32.NewProc("GetModuleHandleW") getModuleHandle = kernel32.NewProc("GetModuleHandleW")
getCurrentThreadId = kernel32.NewProc("GetCurrentThreadId") getCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
getConsoleWindow = kernel32.NewProc("GetConsoleWindow") getConsoleWindow = kernel32.NewProc("GetConsoleWindow")
getSystemDirectory = kernel32.NewProc("GetSystemDirectoryW")
createActCtx = kernel32.NewProc("CreateActCtxW")
activateActCtx = kernel32.NewProc("ActivateActCtx")
deactivateActCtx = kernel32.NewProc("DeactivateActCtx")
coInitializeEx = ole32.NewProc("CoInitializeEx") coInitializeEx = ole32.NewProc("CoInitializeEx")
coUninitialize = ole32.NewProc("CoUninitialize") coUninitialize = ole32.NewProc("CoUninitialize")
@ -98,15 +102,17 @@ func setup() context.CancelFunc {
setForegroundWindow.Call(hwnd) setForegroundWindow.Call(hwnd)
} }
var old uintptr
runtime.LockOSThread() runtime.LockOSThread()
var restore uintptr
cookie := enableVisualStyles()
if setThreadDpiAwarenessContext.Find() == nil { if setThreadDpiAwarenessContext.Find() == nil {
// try: // try:
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE
// DPI_AWARENESS_CONTEXT_SYSTEM_AWARE // DPI_AWARENESS_CONTEXT_SYSTEM_AWARE
for i := -4; i <= -2; i++ { for i := -4; i <= -2; i++ {
restore, _, _ := setThreadDpiAwarenessContext.Call(uintptr(i)) restore, _, _ = setThreadDpiAwarenessContext.Call(uintptr(i))
if restore != 0 { if restore != 0 {
break break
} }
@ -118,8 +124,11 @@ func setup() context.CancelFunc {
icc.ICC = 0x00004020 // ICC_STANDARD_CLASSES|ICC_PROGRESS_CLASS icc.ICC = 0x00004020 // ICC_STANDARD_CLASSES|ICC_PROGRESS_CLASS
return func() { return func() {
if old != 0 { if restore != 0 {
setThreadDpiAwarenessContext.Call(old) setThreadDpiAwarenessContext.Call(restore)
}
if cookie != 0 {
deactivateActCtx.Call(cookie)
} }
runtime.UnlockOSThread() runtime.UnlockOSThread()
} }
@ -145,7 +154,7 @@ func hookDialog(ctx context.Context, initDialog func(wnd uintptr)) (unhook conte
hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET hook, _, err = setWindowsHookEx.Call(12, // WH_CALLWNDPROCRET
syscall.NewCallback(func(code int32, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr { syscall.NewCallback(func(code int32, wparam uintptr, lparam *_CWPRETSTRUCT) uintptr {
if lparam.Message == 0x0110 { // WM_INITDIALOG if lparam.Message == 0x0110 { // WM_INITDIALOG
name := [8]uint16{} var name [8]uint16
getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name))) getClassName.Call(lparam.Wnd, uintptr(unsafe.Pointer(&name)), uintptr(len(name)))
if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box if syscall.UTF16ToString(name[:]) == "#32770" { // The class for a dialog box
var close bool var close bool
@ -290,26 +299,6 @@ func registerClass(instance, proc uintptr) (uintptr, error) {
return ret, err 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 // https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues
func messageLoop(wnd uintptr) error { func messageLoop(wnd uintptr) error {
getMessage := getMessage.Addr() 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 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-cwpretstruct
type _CWPRETSTRUCT struct { type _CWPRETSTRUCT struct {
Result uintptr Result uintptr
@ -420,12 +449,6 @@ type _WNDCLASSEX struct {
IconSm uintptr 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 // https://github.com/wine-mirror/wine/blob/master/include/unknwn.idl
type _IUnknownVtbl struct { type _IUnknownVtbl struct {