From 5e39a48e5c8eaa6f493c37c6992988f5227eb65a Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Thu, 12 May 2022 14:27:24 +0100 Subject: [PATCH] Username (windows). --- cmd/zenity/main.go | 17 +++- pwd.go | 2 +- pwd_test.go | 18 +--- pwd_windows.go | 201 +++++++++++++++++++++++++++++++++++++++++++-- util_unix.go | 17 ++-- 5 files changed, 223 insertions(+), 32 deletions(-) diff --git a/cmd/zenity/main.go b/cmd/zenity/main.go index 0cc5407..30bb5d3 100644 --- a/cmd/zenity/main.go +++ b/cmd/zenity/main.go @@ -158,8 +158,7 @@ func main() { calResult(zenity.Calendar(text, opts...)) case passwordDlg: - _, pw, err := zenity.Password(opts...) - strResult(pw, err) + pwdResult(zenity.Password(opts...)) case fileSelectionDlg: switch { @@ -533,7 +532,9 @@ func strResult(s string, err error) { func lstResult(l []string, err error) { errResult(err) os.Stdout.WriteString(strings.Join(l, zenutil.Separator)) - os.Stdout.WriteString(zenutil.LineBreak) + if len(l) > 0 { + os.Stdout.WriteString(zenutil.LineBreak) + } } func calResult(d time.Time, err error) { @@ -548,6 +549,16 @@ func colResult(c color.Color, err error) { os.Stdout.WriteString(zenutil.LineBreak) } +func pwdResult(u, p string, err error) { + errResult(err) + if username { + os.Stdout.WriteString(u) + os.Stdout.WriteString(zenutil.Separator) + } + os.Stdout.WriteString(p) + os.Stdout.WriteString(zenutil.LineBreak) +} + func ingestPath(path string) string { if runtime.GOOS == "windows" && path != "" { var args []string diff --git a/pwd.go b/pwd.go index dd5b066..3992478 100644 --- a/pwd.go +++ b/pwd.go @@ -4,7 +4,7 @@ package zenity // // Valid options: Title, OKLabel, CancelLabel, ExtraButton, Icon, Username. // -// May return: ErrCanceled, ErrExtraButton, ErrUnsupported. +// May return: ErrCanceled, ErrExtraButton. func Password(options ...Option) (usr string, pwd string, err error) { return password(applyOptions(options)) } diff --git a/pwd_test.go b/pwd_test.go index abfdeec..e843bcf 100644 --- a/pwd_test.go +++ b/pwd_test.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "os" - "runtime" "testing" "time" @@ -60,16 +59,8 @@ func TestPassword_username(t *testing.T) { if skip, err := skip(err); skip { t.Skip("skipping:", err) } - if runtime.GOOS == "windows" { - if errors.Is(err, zenity.ErrUnsupported) { - t.Skip("was not unsupported:", err) - } else { - t.Error("was not unsupported:", err) - } - } else { - if !errors.Is(err, context.Canceled) { - t.Error("was not canceled:", err) - } + if !errors.Is(err, context.Canceled) { + t.Error("was not canceled:", err) } } @@ -84,7 +75,7 @@ func TestPassword_script(t *testing.T) { }{ {name: "Cancel", call: "cancel", err: zenity.ErrCanceled}, {name: "Password", call: "enter pwd", pwd: "pwd"}, - {name: "User", call: "enter usr and pwd (if supported)", usr: "usr", pwd: "pwd", + {name: "User", call: "enter usr and pwd", usr: "usr", pwd: "pwd", opts: []zenity.Option{zenity.Username()}}, } for _, tt := range tests { @@ -94,9 +85,6 @@ func TestPassword_script(t *testing.T) { if skip, err := skip(err); skip { t.Skip("skipping:", err) } - if errors.Is(err, zenity.ErrUnsupported) { - t.Skip("was not unsupported:", err) - } if usr != tt.usr || pwd != tt.pwd || err != tt.err { t.Errorf("Password() = %q, %q, %v; want %q, %q, %v", usr, pwd, err, tt.usr, tt.pwd, tt.err) } diff --git a/pwd_windows.go b/pwd_windows.go index 9fb1faf..a621930 100644 --- a/pwd_windows.go +++ b/pwd_windows.go @@ -1,12 +1,201 @@ package zenity -import "fmt" +import ( + "syscall" + "unsafe" +) func password(opts options) (string, string, error) { - if opts.username { - return "", "", fmt.Errorf("%w: username", ErrUnsupported) + if !opts.username { + opts.hideText = true + str, err := entry("Password:", opts) + return "", str, err } - opts.hideText = true - str, err := entry("Password:", opts) - return "", str, err + + if opts.title == nil { + opts.title = stringPtr("") + } + if opts.okLabel == nil { + opts.okLabel = stringPtr("OK") + } + if opts.cancelLabel == nil { + opts.cancelLabel = stringPtr("Cancel") + } + + dlg := &passwordDialog{} + return dlg.setup(opts) +} + +type passwordDialog struct { + usr string + pwd string + err error + + wnd uintptr + uTextCtl uintptr + uEditCtl uintptr + pTextCtl uintptr + pEditCtl uintptr + okBtn uintptr + cancelBtn uintptr + extraBtn uintptr + font font +} + +func (dlg *passwordDialog) setup(opts options) (string, string, error) { + defer setup()() + dlg.font = getFont() + defer dlg.font.delete() + + if opts.ctx != nil && opts.ctx.Err() != nil { + return "", "", opts.ctx.Err() + } + + instance, _, err := getModuleHandle.Call(0) + if instance == 0 { + return "", "", err + } + + cls, err := registerClass(instance, syscall.NewCallback(passwordProc)) + if cls == 0 { + return "", "", err + } + defer unregisterClass.Call(cls, instance) + + dlg.wnd, _, _ = createWindowEx.Call(_WS_EX_CONTROLPARENT|_WS_EX_WINDOWEDGE|_WS_EX_DLGMODALFRAME, + cls, strptr(*opts.title), + _WS_POPUPWINDOW|_WS_CLIPSIBLINGS|_WS_DLGFRAME, + _CW_USEDEFAULT, _CW_USEDEFAULT, + 281, 191, 0, 0, instance, uintptr(unsafe.Pointer(dlg))) + + dlg.uTextCtl, _, _ = createWindowEx.Call(0, + strptr("STATIC"), strptr("Username:"), + _WS_CHILD|_WS_VISIBLE|_WS_GROUP|_SS_WORDELLIPSIS|_SS_EDITCONTROL|_SS_NOPREFIX, + 12, 10, 241, 16, dlg.wnd, 0, instance, 0) + + var flags uintptr = _WS_CHILD | _WS_VISIBLE | _WS_GROUP | _WS_TABSTOP | _ES_AUTOHSCROLL + dlg.uEditCtl, _, _ = createWindowEx.Call(_WS_EX_CLIENTEDGE, + strptr("EDIT"), 0, + flags, + 12, 30, 241, 24, dlg.wnd, 0, instance, 0) + + dlg.pTextCtl, _, _ = createWindowEx.Call(0, + strptr("STATIC"), strptr("Password:"), + _WS_CHILD|_WS_VISIBLE|_WS_GROUP|_SS_WORDELLIPSIS|_SS_EDITCONTROL|_SS_NOPREFIX, + 12, 60, 241, 16, dlg.wnd, 0, instance, 0) + + dlg.pEditCtl, _, _ = createWindowEx.Call(_WS_EX_CLIENTEDGE, + strptr("EDIT"), 0, + flags|_ES_PASSWORD, + 12, 80, 241, 24, dlg.wnd, 0, instance, 0) + + dlg.okBtn, _, _ = createWindowEx.Call(0, + strptr("BUTTON"), strptr(*opts.okLabel), + _WS_CHILD|_WS_VISIBLE|_WS_GROUP|_WS_TABSTOP|_BS_DEFPUSHBUTTON, + 12, 116, 75, 24, dlg.wnd, _IDOK, instance, 0) + dlg.cancelBtn, _, _ = createWindowEx.Call(0, + strptr("BUTTON"), strptr(*opts.cancelLabel), + _WS_CHILD|_WS_VISIBLE|_WS_GROUP|_WS_TABSTOP, + 12, 116, 75, 24, dlg.wnd, _IDCANCEL, instance, 0) + if opts.extraButton != nil { + dlg.extraBtn, _, _ = createWindowEx.Call(0, + strptr("BUTTON"), strptr(*opts.extraButton), + _WS_CHILD|_WS_VISIBLE|_WS_GROUP|_WS_TABSTOP, + 12, 116, 75, 24, dlg.wnd, _IDNO, instance, 0) + } + + dlg.layout(getDPI(dlg.wnd)) + centerWindow(dlg.wnd) + setFocus.Call(dlg.uEditCtl) + showWindow.Call(dlg.wnd, _SW_NORMAL, 0) + sendMessage.Call(dlg.uEditCtl, _EM_SETSEL, 0, intptr(-1)) + + if opts.ctx != nil { + wait := make(chan struct{}) + defer close(wait) + go func() { + select { + case <-opts.ctx.Done(): + sendMessage.Call(dlg.wnd, _WM_SYSCOMMAND, _SC_CLOSE, 0) + case <-wait: + } + }() + } + + if err := messageLoop(dlg.wnd); err != nil { + return "", "", err + } + if opts.ctx != nil && opts.ctx.Err() != nil { + return "", "", opts.ctx.Err() + } + return dlg.usr, dlg.pwd, dlg.err +} + +func (dlg *passwordDialog) layout(dpi dpi) { + font := dlg.font.forDPI(dpi) + sendMessage.Call(dlg.uTextCtl, _WM_SETFONT, font, 1) + sendMessage.Call(dlg.uEditCtl, _WM_SETFONT, font, 1) + sendMessage.Call(dlg.pTextCtl, _WM_SETFONT, font, 1) + sendMessage.Call(dlg.pEditCtl, _WM_SETFONT, font, 1) + sendMessage.Call(dlg.okBtn, _WM_SETFONT, font, 1) + sendMessage.Call(dlg.cancelBtn, _WM_SETFONT, font, 1) + sendMessage.Call(dlg.extraBtn, _WM_SETFONT, font, 1) + setWindowPos.Call(dlg.wnd, 0, 0, 0, dpi.scale(281), dpi.scale(191), _SWP_NOZORDER|_SWP_NOMOVE) + setWindowPos.Call(dlg.uTextCtl, 0, dpi.scale(12), dpi.scale(10), dpi.scale(241), dpi.scale(16), _SWP_NOZORDER) + setWindowPos.Call(dlg.uEditCtl, 0, dpi.scale(12), dpi.scale(30), dpi.scale(241), dpi.scale(24), _SWP_NOZORDER) + setWindowPos.Call(dlg.pTextCtl, 0, dpi.scale(12), dpi.scale(60), dpi.scale(241), dpi.scale(16), _SWP_NOZORDER) + setWindowPos.Call(dlg.pEditCtl, 0, dpi.scale(12), dpi.scale(80), dpi.scale(241), dpi.scale(24), _SWP_NOZORDER) + if dlg.extraBtn == 0 { + setWindowPos.Call(dlg.okBtn, 0, dpi.scale(95), dpi.scale(116), dpi.scale(75), dpi.scale(24), _SWP_NOZORDER) + setWindowPos.Call(dlg.cancelBtn, 0, dpi.scale(178), dpi.scale(116), dpi.scale(75), dpi.scale(24), _SWP_NOZORDER) + } else { + setWindowPos.Call(dlg.okBtn, 0, dpi.scale(12), dpi.scale(116), dpi.scale(75), dpi.scale(24), _SWP_NOZORDER) + setWindowPos.Call(dlg.extraBtn, 0, dpi.scale(95), dpi.scale(116), dpi.scale(75), dpi.scale(24), _SWP_NOZORDER) + setWindowPos.Call(dlg.cancelBtn, 0, dpi.scale(178), dpi.scale(116), dpi.scale(75), dpi.scale(24), _SWP_NOZORDER) + } +} + +func passwordProc(wnd uintptr, msg uint32, wparam uintptr, lparam *unsafe.Pointer) uintptr { + var dlg *passwordDialog + switch msg { + case _WM_NCCREATE: + saveBackRef(wnd, *lparam) + dlg = (*passwordDialog)(*lparam) + case _WM_NCDESTROY: + deleteBackRef(wnd) + default: + dlg = (*passwordDialog)(loadBackRef(wnd)) + } + + switch msg { + case _WM_DESTROY: + postQuitMessage.Call(0) + + case _WM_CLOSE: + dlg.err = ErrCanceled + destroyWindow.Call(wnd) + + case _WM_COMMAND: + switch wparam { + default: + return 1 + case _IDOK, _IDYES: + dlg.usr = getWindowString(dlg.uEditCtl) + dlg.pwd = getWindowString(dlg.pEditCtl) + case _IDCANCEL: + dlg.err = ErrCanceled + case _IDNO: + dlg.err = ErrExtraButton + } + destroyWindow.Call(wnd) + + case _WM_DPICHANGED: + dlg.layout(dpi(uint32(wparam) >> 16)) + + default: + res, _, _ := defWindowProc.Call(wnd, uintptr(msg), wparam, uintptr(unsafe.Pointer(lparam))) + return res + } + + return 0 } diff --git a/util_unix.go b/util_unix.go index bf39dd7..297cb45 100644 --- a/util_unix.go +++ b/util_unix.go @@ -86,13 +86,16 @@ func lstResult(opts options, out []byte, err error) ([]string, error) { func pwdResult(sep string, opts options, out []byte, err error) (string, string, error) { str, err := strResult(opts, out, err) if opts.username { - if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 255 { - return "", "", ErrUnsupported - } - - if split := strings.SplitN(str, sep, 2); err == nil && len(split) == 2 { - return split[0], split[1], nil - } + usr, pwd, _ := cut(str, sep) + return usr, pwd, err } return "", str, err } + +// 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 { + return s[:i], s[i+len(sep):], true + } + return s, "", false +}