From d33a18d05af48dd05aea633c3242d46534465f98 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Sat, 18 Jun 2022 03:45:49 +0100 Subject: [PATCH] Refactor (windows). --- cmd/zenity/build.sh | 4 + color_windows.go | 37 ++----- file_windows.go | 162 +++++++++---------------------- go.mod | 4 +- go.sum | 8 +- internal/win/comctl32.go | 11 +++ internal/win/comdlg32.go | 80 +++++++++++++++ internal/win/ole32.go | 47 +++++++++ internal/win/pkg.go | 5 + internal/win/shell32.go | 58 +++++++++++ internal/win/zsyscall_windows.go | 131 +++++++++++++++++++++++++ notify_windows.go | 27 +----- util_windows.go | 49 +--------- 13 files changed, 403 insertions(+), 220 deletions(-) create mode 100644 internal/win/comctl32.go create mode 100644 internal/win/comdlg32.go create mode 100644 internal/win/ole32.go create mode 100644 internal/win/pkg.go create mode 100644 internal/win/shell32.go create mode 100644 internal/win/zsyscall_windows.go diff --git a/cmd/zenity/build.sh b/cmd/zenity/build.sh index d8d4962..60eef55 100755 --- a/cmd/zenity/build.sh +++ b/cmd/zenity/build.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -Eeuo pipefail +cd "${BASH_SOURCE%/*}" + +go generate ../../... + TAG=$(git tag --points-at HEAD) echo 'package main; const tag = "'$TAG'"' > tag.go diff --git a/color_windows.go b/color_windows.go index f9a68c3..1394616 100644 --- a/color_windows.go +++ b/color_windows.go @@ -4,11 +4,11 @@ import ( "image/color" "sync" "unsafe" + + "github.com/ncruces/zenity/internal/win" ) var ( - chooseColor = comdlg32.NewProc("ChooseColorW") - savedColors [16]uint32 colorsMutex sync.Mutex ) @@ -19,32 +19,26 @@ func init() { } } -const ( - _CC_RGBINIT = 0x00000001 - _CC_FULLOPEN = 0x00000002 - _CC_PREVENTFULLOPEN = 0x00000004 -) - func selectColor(opts options) (color.Color, error) { // load custom colors colorsMutex.Lock() customColors := savedColors colorsMutex.Unlock() - var args _CHOOSECOLOR + var args win.CHOOSECOLOR args.StructSize = uint32(unsafe.Sizeof(args)) args.Owner, _ = opts.attach.(uintptr) args.CustColors = &customColors if opts.color != nil { - args.Flags |= _CC_RGBINIT + args.Flags |= win.CC_RGBINIT n := color.NRGBAModel.Convert(opts.color).(color.NRGBA) args.RgbResult = uint32(n.R) | uint32(n.G)<<8 | uint32(n.B)<<16 } if opts.showPalette { - args.Flags |= _CC_PREVENTFULLOPEN + args.Flags |= win.CC_PREVENTFULLOPEN } else { - args.Flags |= _CC_FULLOPEN + args.Flags |= win.CC_FULLOPEN } defer setup()() @@ -57,12 +51,12 @@ func selectColor(opts options) (color.Color, error) { defer unhook() } - s, _, _ := chooseColor.Call(uintptr(unsafe.Pointer(&args))) + ok := win.ChooseColor(&args) if opts.ctx != nil && opts.ctx.Err() != nil { return nil, opts.ctx.Err() } - if s == 0 { - return nil, commDlgError() + if !ok { + return nil, win.CommDlgError() } // save custom colors back @@ -75,16 +69,3 @@ func selectColor(opts options) (color.Color, error) { b := uint8(args.RgbResult >> 16) return color.RGBA{R: r, G: g, B: b, A: 255}, nil } - -// https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-choosecolorw-r1 -type _CHOOSECOLOR struct { - StructSize uint32 - Owner uintptr - Instance uintptr - RgbResult uint32 - CustColors *[16]uint32 - Flags uint32 - CustData uintptr - FnHook uintptr - TemplateName *uint16 -} diff --git a/file_windows.go b/file_windows.go index ca90e2c..33c64dc 100644 --- a/file_windows.go +++ b/file_windows.go @@ -7,27 +7,11 @@ import ( "syscall" "unicode/utf16" "unsafe" -) -var ( - getOpenFileName = comdlg32.NewProc("GetOpenFileNameW") - getSaveFileName = comdlg32.NewProc("GetSaveFileNameW") - shBrowseForFolder = shell32.NewProc("SHBrowseForFolderW") - shCreateItemFromParsingName = shell32.NewProc("SHCreateItemFromParsingName") - shGetPathFromIDListEx = shell32.NewProc("SHGetPathFromIDListEx") + "github.com/ncruces/zenity/internal/win" ) const ( - _OFN_OVERWRITEPROMPT = 0x00000002 - _OFN_NOCHANGEDIR = 0x00000008 - _OFN_ALLOWMULTISELECT = 0x00000200 - _OFN_PATHMUSTEXIST = 0x00000800 - _OFN_FILEMUSTEXIST = 0x00001000 - _OFN_CREATEPROMPT = 0x00002000 - _OFN_NOREADONLYRETURN = 0x00008000 - _OFN_EXPLORER = 0x00080000 - _OFN_FORCESHOWHIDDEN = 0x10000000 - _FOS_NOCHANGEDIR = 0x00000008 _FOS_PICKFOLDERS = 0x00000020 _FOS_FORCEFILESYSTEM = 0x00000040 @@ -41,16 +25,16 @@ func selectFile(opts options) (string, error) { return res, err } - var args _OPENFILENAME + var args win.OPENFILENAME args.StructSize = uint32(unsafe.Sizeof(args)) args.Owner, _ = opts.attach.(uintptr) - args.Flags = _OFN_NOCHANGEDIR | _OFN_FILEMUSTEXIST | _OFN_EXPLORER + args.Flags = win.OFN_NOCHANGEDIR | win.OFN_FILEMUSTEXIST | win.OFN_EXPLORER if opts.title != nil { args.Title = syscall.StringToUTF16Ptr(*opts.title) } if opts.showHidden { - args.Flags |= _OFN_FORCESHOWHIDDEN + args.Flags |= win.OFN_FORCESHOWHIDDEN } if opts.fileFilters != nil { args.Filter = &initFilters(opts.fileFilters)[0] @@ -71,12 +55,12 @@ func selectFile(opts options) (string, error) { defer unhook() } - s, _, _ := getOpenFileName.Call(uintptr(unsafe.Pointer(&args))) + ok := win.GetOpenFileName(&args) if opts.ctx != nil && opts.ctx.Err() != nil { return "", opts.ctx.Err() } - if s == 0 { - return "", commDlgError() + if !ok { + return "", win.CommDlgError() } return syscall.UTF16ToString(res[:]), nil } @@ -87,16 +71,16 @@ func selectFileMultiple(opts options) ([]string, error) { return res, err } - var args _OPENFILENAME + var args win.OPENFILENAME args.StructSize = uint32(unsafe.Sizeof(args)) args.Owner, _ = opts.attach.(uintptr) - args.Flags = _OFN_NOCHANGEDIR | _OFN_ALLOWMULTISELECT | _OFN_FILEMUSTEXIST | _OFN_EXPLORER + args.Flags = win.OFN_NOCHANGEDIR | win.OFN_ALLOWMULTISELECT | win.OFN_FILEMUSTEXIST | win.OFN_EXPLORER if opts.title != nil { args.Title = syscall.StringToUTF16Ptr(*opts.title) } if opts.showHidden { - args.Flags |= _OFN_FORCESHOWHIDDEN + args.Flags |= win.OFN_FORCESHOWHIDDEN } if opts.fileFilters != nil { args.Filter = &initFilters(opts.fileFilters)[0] @@ -117,12 +101,12 @@ func selectFileMultiple(opts options) ([]string, error) { defer unhook() } - s, _, _ := getOpenFileName.Call(uintptr(unsafe.Pointer(&args))) + ok := win.GetOpenFileName(&args) if opts.ctx != nil && opts.ctx.Err() != nil { return nil, opts.ctx.Err() } - if s == 0 { - return nil, commDlgError() + if !ok { + return nil, win.CommDlgError() } var i int @@ -158,22 +142,22 @@ func selectFileSave(opts options) (string, error) { return res, err } - var args _OPENFILENAME + var args win.OPENFILENAME args.StructSize = uint32(unsafe.Sizeof(args)) args.Owner, _ = opts.attach.(uintptr) - args.Flags = _OFN_NOCHANGEDIR | _OFN_PATHMUSTEXIST | _OFN_NOREADONLYRETURN | _OFN_EXPLORER + args.Flags = win.OFN_NOCHANGEDIR | win.OFN_PATHMUSTEXIST | win.OFN_NOREADONLYRETURN | win.OFN_EXPLORER if opts.title != nil { args.Title = syscall.StringToUTF16Ptr(*opts.title) } if opts.confirmOverwrite { - args.Flags |= _OFN_OVERWRITEPROMPT + args.Flags |= win.OFN_OVERWRITEPROMPT } if opts.confirmCreate { - args.Flags |= _OFN_CREATEPROMPT + args.Flags |= win.OFN_CREATEPROMPT } if opts.showHidden { - args.Flags |= _OFN_FORCESHOWHIDDEN + args.Flags |= win.OFN_FORCESHOWHIDDEN } if opts.fileFilters != nil { args.Filter = &initFilters(opts.fileFilters)[0] @@ -194,12 +178,12 @@ func selectFileSave(opts options) (string, error) { defer unhook() } - s, _, _ := getSaveFileName.Call(uintptr(unsafe.Pointer(&args))) + ok := win.GetSaveFileName(&args) if opts.ctx != nil && opts.ctx.Err() != nil { return "", opts.ctx.Err() } - if s == 0 { - return "", commDlgError() + if !ok { + return "", win.CommDlgError() } return syscall.UTF16ToString(res[:]), nil } @@ -207,19 +191,19 @@ func selectFileSave(opts options) (string, error) { func pickFolders(opts options, multi bool) (str string, lst []string, err error) { defer setup()() - hr, _, _ := coInitializeEx.Call(0, 0x6) // COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE - if hr != 0x80010106 { // RPC_E_CHANGED_MODE - if int32(hr) < 0 { - return "", nil, syscall.Errno(hr) + err = win.CoInitializeEx(0, 0x6) // COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE + if err != win.RPC_E_CHANGED_MODE { + if err != nil { + return "", nil, err } - defer coUninitialize.Call() + defer win.CoUninitialize() } var dialog *_IFileOpenDialog - hr, _, _ = coCreateInstance.Call( - _CLSID_FileOpenDialog, 0, 0x17, // CLSCTX_ALL - _IID_IFileOpenDialog, uintptr(unsafe.Pointer(&dialog))) - if int32(hr) < 0 { + err = win.CoCreateInstance( + _CLSID_FileOpenDialog, nil, 0x17, // CLSCTX_ALL + _IID_IFileOpenDialog, unsafe.Pointer(&dialog)) + if err != nil { if multi { return "", nil, fmt.Errorf("%w: multiple directory", ErrUnsupported) } @@ -228,7 +212,7 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error) defer dialog.Call(dialog.Release) var flgs int - hr, _, _ = dialog.Call(dialog.GetOptions, uintptr(unsafe.Pointer(&flgs))) + hr, _, _ := dialog.Call(dialog.GetOptions, uintptr(unsafe.Pointer(&flgs))) if int32(hr) < 0 { return "", nil, syscall.Errno(hr) } @@ -250,12 +234,9 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error) } if opts.filename != "" { - var item *_IShellItem + var item *win.IShellItem ptr := syscall.StringToUTF16Ptr(opts.filename) - hr, _, _ = shCreateItemFromParsingName.Call( - uintptr(unsafe.Pointer(ptr)), 0, - _IID_IShellItem, - uintptr(unsafe.Pointer(&item))) + win.SHCreateItemFromParsingName(ptr, nil, _IID_IShellItem, &item) if int32(hr) >= 0 && item != nil { dialog.Call(dialog.SetFolder, uintptr(unsafe.Pointer(item))) @@ -283,8 +264,8 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error) return "", nil, syscall.Errno(hr) } - shellItemPath := func(obj *_COMObject, trap uintptr, a ...uintptr) error { - var item *_IShellItem + shellItemPath := func(obj *win.COMObject, trap uintptr, a ...uintptr) error { + var item *win.IShellItem hr, _, _ := obj.Call(trap, append(a, uintptr(unsafe.Pointer(&item)))...) if int32(hr) < 0 { return syscall.Errno(hr) @@ -298,11 +279,11 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error) if int32(hr) < 0 { return syscall.Errno(hr) } - defer coTaskMemFree.Call(ptr) + defer win.CoTaskMemFree(ptr) var res []uint16 hdr := (*reflect.SliceHeader)(unsafe.Pointer(&res)) - hdr.Data, hdr.Len, hdr.Cap = ptr, 32768, 32768 + hdr.Data, hdr.Len, hdr.Cap = uintptr(ptr), 32768, 32768 str = syscall.UTF16ToString(res) lst = append(lst, str) return nil @@ -322,16 +303,16 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error) return "", nil, syscall.Errno(hr) } for i := uintptr(0); i < uintptr(count) && err == nil; i++ { - err = shellItemPath(&items._COMObject, items.GetItemAt, i) + err = shellItemPath(&items.COMObject, items.GetItemAt, i) } } else { - err = shellItemPath(&dialog._COMObject, dialog.GetResult) + err = shellItemPath(&dialog.COMObject, dialog.GetResult) } return } func browseForFolder(opts options) (string, []string, error) { - var args _BROWSEINFO + var args win.BROWSEINFO args.Owner, _ = opts.attach.(uintptr) args.Flags = 0x1 // BIF_RETURNONLYFSDIRS @@ -351,17 +332,17 @@ func browseForFolder(opts options) (string, []string, error) { defer unhook() } - ptr, _, _ := shBrowseForFolder.Call(uintptr(unsafe.Pointer(&args))) + ptr := win.SHBrowseForFolder(&args) if opts.ctx != nil && opts.ctx.Err() != nil { return "", nil, opts.ctx.Err() } if ptr == 0 { return "", nil, ErrCanceled } - defer coTaskMemFree.Call(ptr) + defer win.CoTaskMemFree(ptr) var res [32768]uint16 - shGetPathFromIDListEx.Call(ptr, uintptr(unsafe.Pointer(&res[0])), uintptr(len(res)), 0) + win.SHGetPathFromIDListEx(ptr, &res[0], len(res), 0) str := syscall.UTF16ToString(res[:]) return str, []string{str}, nil @@ -408,45 +389,6 @@ func initFilters(filters FileFilters) []uint16 { return res } -// https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew -type _OPENFILENAME struct { - StructSize uint32 - Owner uintptr - Instance uintptr - Filter *uint16 - CustomFilter *uint16 - MaxCustomFilter uint32 - FilterIndex uint32 - File *uint16 - MaxFile uint32 - FileTitle *uint16 - MaxFileTitle uint32 - InitialDir *uint16 - Title *uint16 - Flags uint32 - FileOffset uint16 - FileExtension uint16 - DefExt *uint16 - CustData uintptr - FnHook uintptr - TemplateName *uint16 - PvReserved uintptr - DwReserved uint32 - FlagsEx uint32 -} - -// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfow -type _BROWSEINFO struct { - Owner uintptr - Root uintptr - DisplayName *uint16 - Title *uint16 - Flags uint32 - CallbackFunc uintptr - LParam *uint16 - Image int32 -} - // https://github.com/wine-mirror/wine/blob/master/include/shobjidl.idl var ( @@ -456,17 +398,12 @@ var ( ) type _IFileOpenDialog struct { - _COMObject + win.COMObject *_IFileOpenDialogVtbl } -type _IShellItem struct { - _COMObject - *_IShellItemVtbl -} - type _IShellItemArray struct { - _COMObject + win.COMObject *_IShellItemArrayVtbl } @@ -508,15 +445,6 @@ type _IModalWindowVtbl struct { Show uintptr } -type _IShellItemVtbl struct { - _IUnknownVtbl - BindToHandler uintptr - GetParent uintptr - GetDisplayName uintptr - GetAttributes uintptr - Compare uintptr -} - type _IShellItemArrayVtbl struct { _IUnknownVtbl BindToHandler uintptr diff --git a/go.mod b/go.mod index 158374e..2a1b91b 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/ncruces/go-strftime v0.1.8 github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 go.uber.org/goleak v1.1.12 // test - golang.org/x/image v0.0.0-20220601225756-64ec528b34cd - golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d + golang.org/x/image v0.0.0-20220617043117-41969df76e82 + golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c ) require ( diff --git a/go.sum b/go.sum index a4e7300..fdef053 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw= -golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw= +golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -42,8 +42,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/internal/win/comctl32.go b/internal/win/comctl32.go new file mode 100644 index 0000000..63b971c --- /dev/null +++ b/internal/win/comctl32.go @@ -0,0 +1,11 @@ +//go:build windows + +package win + +// https://docs.microsoft.com/en-us/windows/win32/api/commctrl/ns-commctrl-initcommoncontrolsex +type INITCOMMONCONTROLSEX struct { + Size uint32 + ICC uint32 +} + +//sys InitCommonControlsEx(icc *INITCOMMONCONTROLSEX) (ok bool) = comctl32.InitCommonControlsEx diff --git a/internal/win/comdlg32.go b/internal/win/comdlg32.go new file mode 100644 index 0000000..a4f51d6 --- /dev/null +++ b/internal/win/comdlg32.go @@ -0,0 +1,80 @@ +//go:build windows + +package win + +import ( + "fmt" + + "github.com/ncruces/zenity/internal/zenutil" +) + +const ( + CC_RGBINIT = 0x00000001 + CC_FULLOPEN = 0x00000002 + CC_PREVENTFULLOPEN = 0x00000004 +) + +// https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-choosecolorw-r1 +type CHOOSECOLOR struct { + StructSize uint32 + Owner uintptr + Instance uintptr + RgbResult uint32 + CustColors *[16]uint32 + Flags uint32 + CustData uintptr + FnHook uintptr + TemplateName *uint16 +} + +const ( + OFN_OVERWRITEPROMPT = 0x00000002 + OFN_NOCHANGEDIR = 0x00000008 + OFN_ALLOWMULTISELECT = 0x00000200 + OFN_PATHMUSTEXIST = 0x00000800 + OFN_FILEMUSTEXIST = 0x00001000 + OFN_CREATEPROMPT = 0x00002000 + OFN_NOREADONLYRETURN = 0x00008000 + OFN_EXPLORER = 0x00080000 + OFN_FORCESHOWHIDDEN = 0x10000000 +) + +// https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew +type OPENFILENAME struct { + StructSize uint32 + Owner uintptr + Instance uintptr + Filter *uint16 + CustomFilter *uint16 + MaxCustomFilter uint32 + FilterIndex uint32 + File *uint16 + MaxFile uint32 + FileTitle *uint16 + MaxFileTitle uint32 + InitialDir *uint16 + Title *uint16 + Flags uint32 + FileOffset uint16 + FileExtension uint16 + DefExt *uint16 + CustData uintptr + FnHook uintptr + TemplateName *uint16 + PvReserved uintptr + DwReserved uint32 + FlagsEx uint32 +} + +func CommDlgError() error { + if code := commDlgExtendedError(); code == 0 { + return zenutil.ErrCanceled + } else { + return fmt.Errorf("Common Dialog error: %x", code) + } +} + +//sys commDlgExtendedError() (code int) = comdlg32.CommDlgExtendedError +//sys ChooseColor(cc *CHOOSECOLOR) (ok bool) = comdlg32.ChooseColorW +//sys GetOpenFileName(ofn *OPENFILENAME) (ok bool) = comdlg32.GetOpenFileNameW +//sys GetSaveFileName(ofn *OPENFILENAME) (ok bool) = comdlg32.GetSaveFileNameW diff --git a/internal/win/ole32.go b/internal/win/ole32.go new file mode 100644 index 0000000..ea617eb --- /dev/null +++ b/internal/win/ole32.go @@ -0,0 +1,47 @@ +//go:build windows + +package win + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + RPC_E_CHANGED_MODE syscall.Errno = 0x80010106 +) + +func CoInitializeEx(reserved uintptr, coInit uint32) error { + return windows.CoInitializeEx(reserved, coInit) +} + +func CoUninitialize() { windows.CoUninitialize() } + +// https://github.com/wine-mirror/wine/blob/master/include/unknwn.idl + +type IUnknownVtbl struct { + QueryInterface uintptr + AddRef uintptr + Release uintptr +} + +type COMObject struct{} + +//go:uintptrescapes +func (o *COMObject) Call(trap uintptr, a ...uintptr) (r1, r2 uintptr, lastErr error) { + switch nargs := uintptr(len(a)); nargs { + case 0: + return syscall.Syscall(trap, nargs+1, uintptr(unsafe.Pointer(o)), 0, 0) + case 1: + return syscall.Syscall(trap, nargs+1, uintptr(unsafe.Pointer(o)), a[0], 0) + case 2: + return syscall.Syscall(trap, nargs+1, uintptr(unsafe.Pointer(o)), a[0], a[1]) + default: + panic("COM call with too many arguments.") + } +} + +//sys CoTaskMemFree(address uintptr) = ole32.CoTaskMemFree +//sys CoCreateInstance(clsid uintptr, unkOuter unsafe.Pointer, clsContext int32, iid uintptr, address unsafe.Pointer) (ret error) = ole32.CoCreateInstance diff --git a/internal/win/pkg.go b/internal/win/pkg.go new file mode 100644 index 0000000..3bca0c0 --- /dev/null +++ b/internal/win/pkg.go @@ -0,0 +1,5 @@ +// Package win is internal. DO NOT USE. +package win + +//go:generate -command mkwinsyscall go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go +//go:generate mkwinsyscall comctl32.go comdlg32.go ole32.go shell32.go diff --git a/internal/win/shell32.go b/internal/win/shell32.go new file mode 100644 index 0000000..dc49774 --- /dev/null +++ b/internal/win/shell32.go @@ -0,0 +1,58 @@ +//go:build windows + +package win + +const ( + NIM_ADD = 0 + NIM_DELETE = 2 +) + +// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfow +type BROWSEINFO struct { + Owner uintptr + Root uintptr + DisplayName *uint16 + Title *uint16 + Flags uint32 + CallbackFunc uintptr + LParam *uint16 + Image int32 +} + +// https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataw +type NOTIFYICONDATA struct { + StructSize uint32 + Wnd uintptr + ID uint32 + Flags uint32 + CallbackMessage uint32 + Icon uintptr + Tip [128]uint16 // NOTIFYICONDATAA_V1_SIZE + State uint32 + StateMask uint32 + Info [256]uint16 + Version uint32 + InfoTitle [64]uint16 + InfoFlags uint32 + // GuidItem [16]byte // NOTIFYICONDATAA_V2_SIZE + // BalloonIcon syscall.Handle // NOTIFYICONDATAA_V3_SIZE +} + +type IShellItem struct { + COMObject + *_IShellItemVtbl +} + +type _IShellItemVtbl struct { + IUnknownVtbl + BindToHandler uintptr + GetParent uintptr + GetDisplayName uintptr + GetAttributes uintptr + Compare uintptr +} + +//sys SHBrowseForFolder(bi *BROWSEINFO) (ptr uintptr) = shell32.SHBrowseForFolder +//sys SHCreateItemFromParsingName(path *uint16, bc unsafe.Pointer, iid uintptr, item **IShellItem) (err error) = shell32.SHCreateItemFromParsingName +//sys SHGetPathFromIDListEx(ptr uintptr, path *uint16, pathLen int, opts int) (err error) = shell32.SHGetPathFromIDListEx +//sys ShellNotifyIcon(message uint32, data *NOTIFYICONDATA) (ret int, err error) = shell32.Shell_NotifyIconW diff --git a/internal/win/zsyscall_windows.go b/internal/win/zsyscall_windows.go new file mode 100644 index 0000000..6b347f4 --- /dev/null +++ b/internal/win/zsyscall_windows.go @@ -0,0 +1,131 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package win + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modcomctl32 = windows.NewLazySystemDLL("comctl32.dll") + modcomdlg32 = windows.NewLazySystemDLL("comdlg32.dll") + modole32 = windows.NewLazySystemDLL("ole32.dll") + modshell32 = windows.NewLazySystemDLL("shell32.dll") + + procInitCommonControlsEx = modcomctl32.NewProc("InitCommonControlsEx") + procChooseColorW = modcomdlg32.NewProc("ChooseColorW") + procCommDlgExtendedError = modcomdlg32.NewProc("CommDlgExtendedError") + procGetOpenFileNameW = modcomdlg32.NewProc("GetOpenFileNameW") + procGetSaveFileNameW = modcomdlg32.NewProc("GetSaveFileNameW") + procCoCreateInstance = modole32.NewProc("CoCreateInstance") + procCoTaskMemFree = modole32.NewProc("CoTaskMemFree") + procSHBrowseForFolder = modshell32.NewProc("SHBrowseForFolder") + procSHCreateItemFromParsingName = modshell32.NewProc("SHCreateItemFromParsingName") + procSHGetPathFromIDListEx = modshell32.NewProc("SHGetPathFromIDListEx") + procShell_NotifyIconW = modshell32.NewProc("Shell_NotifyIconW") +) + +func InitCommonControlsEx(icc *INITCOMMONCONTROLSEX) (ok bool) { + r0, _, _ := syscall.Syscall(procInitCommonControlsEx.Addr(), 1, uintptr(unsafe.Pointer(icc)), 0, 0) + ok = r0 != 0 + return +} + +func ChooseColor(cc *CHOOSECOLOR) (ok bool) { + r0, _, _ := syscall.Syscall(procChooseColorW.Addr(), 1, uintptr(unsafe.Pointer(cc)), 0, 0) + ok = r0 != 0 + return +} + +func commDlgExtendedError() (code int) { + r0, _, _ := syscall.Syscall(procCommDlgExtendedError.Addr(), 0, 0, 0, 0) + code = int(r0) + return +} + +func GetOpenFileName(ofn *OPENFILENAME) (ok bool) { + r0, _, _ := syscall.Syscall(procGetOpenFileNameW.Addr(), 1, uintptr(unsafe.Pointer(ofn)), 0, 0) + ok = r0 != 0 + return +} + +func GetSaveFileName(ofn *OPENFILENAME) (ok bool) { + r0, _, _ := syscall.Syscall(procGetSaveFileNameW.Addr(), 1, uintptr(unsafe.Pointer(ofn)), 0, 0) + ok = r0 != 0 + return +} + +func CoCreateInstance(clsid uintptr, unkOuter unsafe.Pointer, clsContext int32, iid uintptr, address unsafe.Pointer) (ret error) { + r0, _, _ := syscall.Syscall6(procCoCreateInstance.Addr(), 5, uintptr(clsid), uintptr(unkOuter), uintptr(clsContext), uintptr(iid), uintptr(address), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func CoTaskMemFree(address uintptr) { + syscall.Syscall(procCoTaskMemFree.Addr(), 1, uintptr(address), 0, 0) + return +} + +func SHBrowseForFolder(bi *BROWSEINFO) (ptr uintptr) { + r0, _, _ := syscall.Syscall(procSHBrowseForFolder.Addr(), 1, uintptr(unsafe.Pointer(bi)), 0, 0) + ptr = uintptr(r0) + return +} + +func SHCreateItemFromParsingName(path *uint16, bc unsafe.Pointer, iid uintptr, item **IShellItem) (err error) { + r1, _, e1 := syscall.Syscall6(procSHCreateItemFromParsingName.Addr(), 4, uintptr(unsafe.Pointer(path)), uintptr(bc), uintptr(iid), uintptr(unsafe.Pointer(item)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SHGetPathFromIDListEx(ptr uintptr, path *uint16, pathLen int, opts int) (err error) { + r1, _, e1 := syscall.Syscall6(procSHGetPathFromIDListEx.Addr(), 4, uintptr(ptr), uintptr(unsafe.Pointer(path)), uintptr(pathLen), uintptr(opts), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func ShellNotifyIcon(message uint32, data *NOTIFYICONDATA) (ret int, err error) { + r0, _, e1 := syscall.Syscall(procShell_NotifyIconW.Addr(), 2, uintptr(message), uintptr(unsafe.Pointer(data)), 0) + ret = int(r0) + if ret == 0 { + err = errnoErr(e1) + } + return +} diff --git a/notify_windows.go b/notify_windows.go index 1b4003d..9dc2db8 100644 --- a/notify_windows.go +++ b/notify_windows.go @@ -7,12 +7,12 @@ import ( "time" "unsafe" + "github.com/ncruces/zenity/internal/win" "github.com/ncruces/zenity/internal/zenutil" ) var ( rtlGetNtVersionNumbers = ntdll.NewProc("RtlGetNtVersionNumbers") - shellNotifyIcon = shell32.NewProc("Shell_NotifyIconW") wtsSendMessage = wtsapi32.NewProc("WTSSendMessageW") ) @@ -21,7 +21,7 @@ func notify(text string, opts options) error { return opts.ctx.Err() } - var args _NOTIFYICONDATA + var args win.NOTIFYICONDATA args.StructSize = uint32(unsafe.Sizeof(args)) args.ID = rand.Uint32() args.Flags = 0x00000010 // NIF_INFO @@ -55,7 +55,7 @@ func notify(text string, opts options) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - s, _, err := shellNotifyIcon.Call(0 /* NIM_ADD */, uintptr(unsafe.Pointer(&args))) + s, err := win.ShellNotifyIcon(win.NIM_ADD, &args) if s == 0 { if errno, ok := err.(syscall.Errno); ok && errno == 0 { return wtsMessage(text, opts) @@ -81,7 +81,7 @@ func notify(text string, opts options) error { } } - shellNotifyIcon.Call(2 /* NIM_DELETE */, uintptr(unsafe.Pointer(&args))) + win.ShellNotifyIcon(win.NIM_DELETE, &args) return nil } @@ -125,22 +125,3 @@ func wtsMessage(text string, opts options) error { } return nil } - -// https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataw -type _NOTIFYICONDATA struct { - StructSize uint32 - Wnd uintptr - ID uint32 - Flags uint32 - CallbackMessage uint32 - Icon uintptr - Tip [128]uint16 // NOTIFYICONDATAA_V1_SIZE - State uint32 - StateMask uint32 - Info [256]uint16 - Version uint32 - InfoTitle [64]uint16 - InfoFlags uint32 - // GuidItem [16]byte // NOTIFYICONDATAA_V2_SIZE - // BalloonIcon syscall.Handle // NOTIFYICONDATAA_V3_SIZE -} diff --git a/util_windows.go b/util_windows.go index cfdc602..178203c 100644 --- a/util_windows.go +++ b/util_windows.go @@ -3,7 +3,6 @@ package zenity import ( "bytes" "context" - "fmt" "os" "reflect" "runtime" @@ -13,23 +12,17 @@ import ( "syscall" "unsafe" + "github.com/ncruces/zenity/internal/win" "golang.org/x/sys/windows" ) var ( - comctl32 = windows.NewLazySystemDLL("comctl32.dll") - comdlg32 = windows.NewLazySystemDLL("comdlg32.dll") gdi32 = windows.NewLazySystemDLL("gdi32.dll") kernel32 = windows.NewLazySystemDLL("kernel32.dll") ntdll = windows.NewLazySystemDLL("ntdll.dll") - ole32 = windows.NewLazySystemDLL("ole32.dll") - shell32 = windows.NewLazySystemDLL("shell32.dll") user32 = windows.NewLazySystemDLL("user32.dll") wtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll") - commDlgExtendedError = comdlg32.NewProc("CommDlgExtendedError") - initCommonControlsEx = comctl32.NewProc("InitCommonControlsEx") - createFontIndirect = gdi32.NewProc("CreateFontIndirectW") deleteObject = gdi32.NewProc("DeleteObject") getDeviceCaps = gdi32.NewProc("GetDeviceCaps") @@ -42,11 +35,6 @@ var ( getModuleHandle = kernel32.NewProc("GetModuleHandleW") getSystemDirectory = kernel32.NewProc("GetSystemDirectoryW") - coCreateInstance = ole32.NewProc("CoCreateInstance") - coInitializeEx = ole32.NewProc("CoInitializeEx") - coTaskMemFree = ole32.NewProc("CoTaskMemFree") - coUninitialize = ole32.NewProc("CoUninitialize") - callNextHookEx = user32.NewProc("CallNextHookEx") createIconFromResource = user32.NewProc("CreateIconFromResource") createWindowEx = user32.NewProc("CreateWindowExW") @@ -121,10 +109,10 @@ func setup() context.CancelFunc { } } - var icc _INITCOMMONCONTROLSEX + var icc win.INITCOMMONCONTROLSEX icc.Size = uint32(unsafe.Sizeof(icc)) icc.ICC = 0x00004020 // ICC_STANDARD_CLASSES|ICC_PROGRESS_CLASS - initCommonControlsEx.Call(uintptr(unsafe.Pointer(&icc))) + win.InitCommonControlsEx(&icc) return func() { if restore != 0 { @@ -147,15 +135,6 @@ func setupEnumCallback(wnd uintptr, lparam *uintptr) uintptr { return 1 // continue enumeration } -func commDlgError() error { - s, _, _ := commDlgExtendedError.Call() - if s == 0 { - return ErrCanceled - } else { - return fmt.Errorf("Common Dialog error: %x", s) - } -} - func hookDialog(ctx context.Context, icon any, title *string, init func(wnd uintptr)) (unhook context.CancelFunc, err error) { if ctx != nil && ctx.Err() != nil { return nil, ctx.Err() @@ -482,12 +461,6 @@ type _ACTCTX struct { 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 @@ -596,19 +569,3 @@ type _IUnknownVtbl struct { func uuid(s string) uintptr { return (*reflect.StringHeader)(unsafe.Pointer(&s)).Data } - -type _COMObject struct{} - -//go:uintptrescapes -func (o *_COMObject) Call(trap uintptr, a ...uintptr) (r1, r2 uintptr, lastErr error) { - switch nargs := uintptr(len(a)); nargs { - case 0: - return syscall.Syscall(trap, nargs+1, uintptr(unsafe.Pointer(o)), 0, 0) - case 1: - return syscall.Syscall(trap, nargs+1, uintptr(unsafe.Pointer(o)), a[0], 0) - case 2: - return syscall.Syscall(trap, nargs+1, uintptr(unsafe.Pointer(o)), a[0], a[1]) - default: - panic("COM call with too many arguments.") - } -}