diff --git a/color_windows.go b/color_windows.go index 4b98420..66761dc 100644 --- a/color_windows.go +++ b/color_windows.go @@ -41,7 +41,7 @@ func selectColor(opts options) (color.Color, error) { args.Flags |= win.CC_FULLOPEN } - defer setup()() + defer setup(args.Owner)() unhook, err := hookDialog(opts.ctx, opts.windowIcon, opts.title, nil) if err != nil { return nil, err diff --git a/date_windows.go b/date_windows.go index c57da55..188347d 100644 --- a/date_windows.go +++ b/date_windows.go @@ -37,7 +37,8 @@ type calendarDialog struct { } func (dlg *calendarDialog) setup(text string, opts options) (time.Time, error) { - defer setup()() + owner, _ := opts.attach.(win.HWND) + defer setup(owner)() dlg.font = getFont() defer dlg.font.delete() icon := getIcon(opts.windowIcon) @@ -58,7 +59,6 @@ func (dlg *calendarDialog) setup(text string, opts options) (time.Time, error) { } defer win.UnregisterClass(cls, instance) - owner, _ := opts.attach.(win.HWND) dlg.wnd, _ = win.CreateWindowEx(_WS_EX_ZEN_DIALOG, cls, strptr(*opts.title), _WS_ZEN_DIALOG, win.CW_USEDEFAULT, win.CW_USEDEFAULT, diff --git a/entry_windows.go b/entry_windows.go index 0c54628..0bc30ea 100644 --- a/entry_windows.go +++ b/entry_windows.go @@ -36,7 +36,8 @@ type entryDialog struct { } func (dlg *entryDialog) setup(text string, opts options) (string, error) { - defer setup()() + owner, _ := opts.attach.(win.HWND) + defer setup(owner)() dlg.font = getFont() defer dlg.font.delete() icon := getIcon(opts.windowIcon) @@ -57,7 +58,6 @@ func (dlg *entryDialog) setup(text string, opts options) (string, error) { } defer win.UnregisterClass(cls, instance) - owner, _ := opts.attach.(win.HWND) dlg.wnd, _ = win.CreateWindowEx(_WS_EX_ZEN_DIALOG, cls, strptr(*opts.title), _WS_ZEN_DIALOG, win.CW_USEDEFAULT, win.CW_USEDEFAULT, diff --git a/file_windows.go b/file_windows.go index 5977ad8..a059a57 100644 --- a/file_windows.go +++ b/file_windows.go @@ -36,7 +36,7 @@ func selectFile(opts options) (string, error) { args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) - defer setup()() + defer setup(args.Owner)() unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) if err != nil { return "", err @@ -79,7 +79,7 @@ func selectFileMultiple(opts options) ([]string, error) { args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) - defer setup()() + defer setup(args.Owner)() unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) if err != nil { return nil, err @@ -153,7 +153,7 @@ func selectFileSave(opts options) (string, error) { args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) - defer setup()() + defer setup(args.Owner)() unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) if err != nil { return "", err @@ -171,7 +171,8 @@ func selectFileSave(opts options) (string, error) { } func pickFolders(opts options, multi bool) (string, []string, error) { - defer setup()() + owner, _ := opts.attach.(win.HWND) + defer setup(owner)() err := win.CoInitializeEx(0, win.COINIT_APARTMENTTHREADED|win.COINIT_DISABLE_OLE1DDE) if err != win.RPC_E_CHANGED_MODE { @@ -230,7 +231,6 @@ func pickFolders(opts options, multi bool) (string, []string, error) { defer unhook() } - owner, _ := opts.attach.(win.HWND) err = dialog.Show(owner) if opts.ctx != nil && opts.ctx.Err() != nil { return "", nil, opts.ctx.Err() diff --git a/internal/win/comctl32.go b/internal/win/comctl32.go index ea58ca8..0e96494 100644 --- a/internal/win/comctl32.go +++ b/internal/win/comctl32.go @@ -3,6 +3,7 @@ package win const ( + // InitCommonControlsEx flags ICC_LISTVIEW_CLASSES = 0x00000001 ICC_TREEVIEW_CLASSES = 0x00000002 ICC_BAR_CLASSES = 0x00000004 diff --git a/internal/win/comdlg32.go b/internal/win/comdlg32.go index 60da766..fb1cf53 100644 --- a/internal/win/comdlg32.go +++ b/internal/win/comdlg32.go @@ -9,6 +9,7 @@ import ( ) const ( + // ChooseColor flags CC_RGBINIT = 0x00000001 CC_FULLOPEN = 0x00000002 CC_PREVENTFULLOPEN = 0x00000004 diff --git a/internal/win/kernel32.go b/internal/win/kernel32.go index 0f87f2c..7f44fe7 100644 --- a/internal/win/kernel32.go +++ b/internal/win/kernel32.go @@ -5,6 +5,7 @@ package win import "golang.org/x/sys/windows" const ( + // CreateActCtx flags ACTCTX_FLAG_PROCESSOR_ARCHITECTURE_VALID = 0x001 ACTCTX_FLAG_LANGID_VALID = 0x002 ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID = 0x004 @@ -12,6 +13,13 @@ const ( ACTCTX_FLAG_SET_PROCESS_DEFAULT = 0x010 ACTCTX_FLAG_APPLICATION_NAME_VALID = 0x020 ACTCTX_FLAG_HMODULE_VALID = 0x080 + + // Control signals + CTRL_C_EVENT = 0 + CTRL_BREAK_EVENT = 1 + CTRL_CLOSE_EVENT = 2 + CTRL_LOGOFF_EVENT = 5 + CTRL_SHUTDOWN_EVENT = 6 ) // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-actctxw @@ -33,6 +41,7 @@ func GetSystemDirectory() (string, error) { return windows.GetSystemDirectory() //sys ActivateActCtx(actCtx Handle, cookie *uintptr) (err error) = kernel32.ActivateActCtx //sys CreateActCtx(actCtx *ACTCTX) (ret Handle, err error) = kernel32.CreateActCtxW //sys DeactivateActCtx(flags uint32, cookie uintptr) (err error) = kernel32.DeactivateActCtx +//sys GenerateConsoleCtrlEvent(ctrlEvent uint32, processGroupId int) (err error) = kernel32.GenerateConsoleCtrlEvent //sys GetConsoleWindow() (ret HWND) = kernel32.GetConsoleWindow //sys GetModuleHandle(moduleName *uint16) (ret Handle, err error) = kernel32.GetModuleHandleW //sys ReleaseActCtx(actCtx Handle) = kernel32.ReleaseActCtx diff --git a/internal/win/shell32.go b/internal/win/shell32.go index 829a632..0a05e2f 100644 --- a/internal/win/shell32.go +++ b/internal/win/shell32.go @@ -71,6 +71,7 @@ const ( FOS_FORCEPREVIEWPANEON = 0x40000000 FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 + // IShellItem.GetDisplayName forms SIGDN_NORMALDISPLAY = 0x00000000 SIGDN_PARENTRELATIVEPARSING = ^(^0x18001 + 0x80000000) SIGDN_DESKTOPABSOLUTEPARSING = ^(^0x28000 + 0x80000000) diff --git a/internal/win/zsyscall_windows.go b/internal/win/zsyscall_windows.go index 4ac75dd..d28d2ae 100644 --- a/internal/win/zsyscall_windows.go +++ b/internal/win/zsyscall_windows.go @@ -58,6 +58,7 @@ var ( procActivateActCtx = modkernel32.NewProc("ActivateActCtx") procCreateActCtxW = modkernel32.NewProc("CreateActCtxW") procDeactivateActCtx = modkernel32.NewProc("DeactivateActCtx") + procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent") procGetConsoleWindow = modkernel32.NewProc("GetConsoleWindow") procGetModuleHandleW = modkernel32.NewProc("GetModuleHandleW") procReleaseActCtx = modkernel32.NewProc("ReleaseActCtx") @@ -179,6 +180,14 @@ func DeactivateActCtx(flags uint32, cookie uintptr) (err error) { return } +func GenerateConsoleCtrlEvent(ctrlEvent uint32, processGroupId int) (err error) { + r1, _, e1 := syscall.Syscall(procGenerateConsoleCtrlEvent.Addr(), 2, uintptr(ctrlEvent), uintptr(processGroupId), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func GetConsoleWindow() (ret HWND) { r0, _, _ := syscall.Syscall(procGetConsoleWindow.Addr(), 0, 0, 0, 0) ret = HWND(r0) diff --git a/internal/zencmd/process_windows.go b/internal/zencmd/process_windows.go index f279430..bf9cebf 100644 --- a/internal/zencmd/process_windows.go +++ b/internal/zencmd/process_windows.go @@ -1,4 +1,8 @@ package zencmd +import "github.com/ncruces/zenity/internal/win" + // KillParent is internal. -func KillParent() {} +func KillParent() { + win.GenerateConsoleCtrlEvent(win.CTRL_BREAK_EVENT, 0) +} diff --git a/internal/zencmd/window_windows.go b/internal/zencmd/window_windows.go index 24ff5fe..69ed78d 100644 --- a/internal/zencmd/window_windows.go +++ b/internal/zencmd/window_windows.go @@ -3,6 +3,7 @@ package zencmd import ( "strconv" + "github.com/ncruces/zenity/internal/win" "golang.org/x/sys/windows" ) @@ -13,6 +14,6 @@ func ParseWindowId(id string) windows.HWND { } // GetParentWindowId is internal. -func GetParentWindowId(pid int) int { - return 0 +func GetParentWindowId(pid int) windows.HWND { + return win.GetConsoleWindow() } diff --git a/list_windows.go b/list_windows.go index 3daa229..a9245b9 100644 --- a/list_windows.go +++ b/list_windows.go @@ -53,7 +53,8 @@ type listDialog struct { } func (dlg *listDialog) setup(text string, opts options) ([]string, error) { - defer setup()() + owner, _ := opts.attach.(win.HWND) + defer setup(owner)() dlg.font = getFont() defer dlg.font.delete() icon := getIcon(opts.windowIcon) @@ -74,7 +75,6 @@ func (dlg *listDialog) setup(text string, opts options) ([]string, error) { } defer win.UnregisterClass(cls, instance) - owner, _ := opts.attach.(win.HWND) dlg.wnd, _ = win.CreateWindowEx(_WS_EX_ZEN_DIALOG, cls, strptr(*opts.title), _WS_ZEN_DIALOG, win.CW_USEDEFAULT, win.CW_USEDEFAULT, diff --git a/msg_windows.go b/msg_windows.go index 988311f..b78bee7 100644 --- a/msg_windows.go +++ b/msg_windows.go @@ -52,7 +52,8 @@ func message(kind messageKind, text string, opts options) error { } } - defer setup()() + owner, _ := opts.attach.(win.HWND) + defer setup(owner)() unhook, err := hookMessageDialog(opts) if err != nil { return err @@ -64,7 +65,6 @@ func message(kind messageKind, text string, opts options) error { title = strptr(*opts.title) } - owner, _ := opts.attach.(win.HWND) s, err := win.MessageBox(owner, strptr(text), title, flags) if opts.ctx != nil && opts.ctx.Err() != nil { diff --git a/progress_windows.go b/progress_windows.go index 4828c1a..4485e26 100644 --- a/progress_windows.go +++ b/progress_windows.go @@ -116,7 +116,8 @@ func (dlg *progressDialog) setup(opts options) error { var once sync.Once defer once.Do(dlg.init.Done) - defer setup()() + owner, _ := opts.attach.(win.HWND) + defer setup(owner)() dlg.font = getFont() defer dlg.font.delete() icon := getIcon(opts.windowIcon) @@ -137,7 +138,6 @@ func (dlg *progressDialog) setup(opts options) error { } defer win.UnregisterClass(cls, instance) - owner, _ := opts.attach.(win.HWND) dlg.wnd, _ = win.CreateWindowEx(_WS_EX_ZEN_DIALOG, cls, strptr(*opts.title), _WS_ZEN_DIALOG, win.CW_USEDEFAULT, win.CW_USEDEFAULT, diff --git a/pwd_windows.go b/pwd_windows.go index 4315dab..9e915a0 100644 --- a/pwd_windows.go +++ b/pwd_windows.go @@ -46,7 +46,8 @@ type passwordDialog struct { } func (dlg *passwordDialog) setup(opts options) (string, string, error) { - defer setup()() + owner, _ := opts.attach.(win.HWND) + defer setup(owner)() dlg.font = getFont() defer dlg.font.delete() icon := getIcon(opts.windowIcon) @@ -67,7 +68,6 @@ func (dlg *passwordDialog) setup(opts options) (string, string, error) { } defer win.UnregisterClass(cls, instance) - owner, _ := opts.attach.(win.HWND) dlg.wnd, _ = win.CreateWindowEx(_WS_EX_ZEN_DIALOG, cls, strptr(*opts.title), _WS_ZEN_DIALOG, win.CW_USEDEFAULT, win.CW_USEDEFAULT, diff --git a/util_unix.go b/util_unix.go index f427127..111cd47 100644 --- a/util_unix.go +++ b/util_unix.go @@ -5,7 +5,6 @@ package zenity import ( "bytes" "os/exec" - "reflect" "strconv" "strings" @@ -99,10 +98,6 @@ func pwdResult(sep string, opts options, out []byte, err error) (string, string, return "", str, err } -func hwnd(v reflect.Value) uintptr { - return uintptr(v.Uint()) -} - // Replace with strings.Cut after 1.18. func cut(s, sep string) (before, after string, found bool) { if i := strings.Index(s, sep); i >= 0 { diff --git a/util_windows.go b/util_windows.go index e7e61cb..581afcc 100644 --- a/util_windows.go +++ b/util_windows.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "os" - "reflect" "runtime" "strconv" "sync" @@ -26,13 +25,10 @@ const ( func intptr(i int64) uintptr { return uintptr(i) } func strptr(s string) *uint16 { return syscall.StringToUTF16Ptr(s) } -func hwnd(v reflect.Value) win.HWND { - return win.HWND(uintptr(v.Uint())) -} - -func setup() context.CancelFunc { - var wnd win.HWND - win.EnumWindows(syscall.NewCallback(setupEnumCallback), unsafe.Pointer(&wnd)) +func setup(wnd win.HWND) context.CancelFunc { + if wnd == 0 { + win.EnumWindows(syscall.NewCallback(setupEnumCallback), unsafe.Pointer(&wnd)) + } if wnd == 0 { wnd = win.GetConsoleWindow() } diff --git a/zenity.go b/zenity.go index 8056c08..caefe0f 100644 --- a/zenity.go +++ b/zenity.go @@ -13,8 +13,6 @@ package zenity import ( "context" "image/color" - "reflect" - "runtime" "time" "github.com/ncruces/zenity/internal/zenutil" @@ -191,38 +189,6 @@ func CustomIcon(path string) Option { return Icon(path) } -// Attach returns an Option to set the parent window to attach to. -// -// Attach accepts: -// - a window id (int) on Unix -// - a window handle (~uintptr) on Windows -// - an application name (string) or process id (int) on macOS -func Attach(id any) Option { - switch runtime.GOOS { - case "windows": - if v := reflect.ValueOf(id); v.Kind() == reflect.Uintptr { - id = hwnd(v) - } else { - panic("interface conversion: expected uintptr") - } - - case "darwin": - switch id.(type) { - case int, string: - default: - panic("interface conversion: expected int or string") - } - - default: - switch id.(type) { - case int: - default: - panic("interface conversion: expected int") - } - } - return funcOption(func(o *options) { o.attach = id }) -} - // Modal returns an Option to set the modal hint. func Modal() Option { return funcOption(func(o *options) { o.modal = true }) diff --git a/zenity_darwin.go b/zenity_darwin.go new file mode 100644 index 0000000..e4b40c7 --- /dev/null +++ b/zenity_darwin.go @@ -0,0 +1,16 @@ +package zenity + +// Attach returns an Option to set the parent window to attach to. +// +// Attach accepts: +// - a window id (int) on Unix +// - a window handle (~uintptr) on Windows +// - an application name (string) or process id (int) on macOS +func Attach(id any) Option { + switch id.(type) { + case int, string: + default: + panic("interface conversion: expected int or string") + } + return funcOption(func(o *options) { o.attach = id }) +} diff --git a/zenity_unix.go b/zenity_unix.go new file mode 100644 index 0000000..45fd72b --- /dev/null +++ b/zenity_unix.go @@ -0,0 +1,13 @@ +//go:build !windows && !darwin + +package zenity + +// Attach returns an Option to set the parent window to attach to. +// +// Attach accepts: +// - a window id (int) on Unix +// - a window handle (~uintptr) on Windows +// - an application name (string) or process id (int) on macOS +func Attach(id int) Option { + return funcOption(func(o *options) { o.attach = id }) +} diff --git a/zenity_windows.go b/zenity_windows.go new file mode 100644 index 0000000..0344381 --- /dev/null +++ b/zenity_windows.go @@ -0,0 +1,22 @@ +package zenity + +import ( + "reflect" + + "github.com/ncruces/zenity/internal/win" +) + +// Attach returns an Option to set the parent window to attach to. +// +// Attach accepts: +// - a window id (int) on Unix +// - a window handle (~uintptr) on Windows +// - an application name (string) or process id (int) on macOS +func Attach(id any) Option { + if v := reflect.ValueOf(id); v.Kind() == reflect.Uintptr { + id = win.HWND(uintptr(v.Uint())) + } else { + panic("interface conversion: expected uintptr") + } + return funcOption(func(o *options) { o.attach = id }) +}