zenity/entry_windows.go

364 lines
10 KiB
Go
Raw Normal View History

2021-03-07 19:57:16 -05:00
package zenity
2018-01-25 10:37:06 -05:00
import (
2021-04-05 09:58:30 -04:00
"strconv"
2018-01-25 10:37:06 -05:00
"syscall"
"unsafe"
)
2021-03-07 19:57:16 -05:00
func entry(text string, opts options) (string, bool, error) {
var title string
if opts.title != nil {
title = *opts.title
}
2021-04-05 12:54:46 -04:00
if opts.okLabel == nil {
opts.okLabel = stringPtr("OK")
}
if opts.cancelLabel == nil {
opts.cancelLabel = stringPtr("Cancel")
}
return editBox(title, text, opts)
2021-03-07 19:57:16 -05:00
}
func password(opts options) (string, string, bool, error) {
2021-04-05 12:54:46 -04:00
opts.hideText = true
pass, ok, err := entry("Password:", opts)
2021-03-07 19:57:16 -05:00
return "", pass, ok, err
}
2018-01-25 10:37:06 -05:00
var (
2021-04-05 12:54:46 -04:00
registerClassEx = user32.NewProc("RegisterClassExW")
unregisterClass = user32.NewProc("UnregisterClassW")
2021-03-29 14:07:44 -04:00
createWindowEx = user32.NewProc("CreateWindowExW")
destroyWindow = user32.NewProc("DestroyWindow")
2021-04-05 12:54:46 -04:00
isDialogMessage = user32.NewProc("IsDialogMessageW")
translateMessage = user32.NewProc("TranslateMessage")
2021-03-29 14:07:44 -04:00
dispatchMessage = user32.NewProc("DispatchMessageW")
postQuitMessage = user32.NewProc("PostQuitMessage")
2021-04-05 12:54:46 -04:00
defWindowProc = user32.NewProc("DefWindowProcW")
2021-03-29 14:07:44 -04:00
getWindowRect = user32.NewProc("GetWindowRect")
setWindowPos = user32.NewProc("SetWindowPos")
2021-04-05 12:54:46 -04:00
setFocus = user32.NewProc("SetFocus")
2021-03-29 14:07:44 -04:00
showWindow = user32.NewProc("ShowWindow")
systemParametersInfo = user32.NewProc("SystemParametersInfoW")
2021-04-05 12:54:46 -04:00
getSystemMetrics = user32.NewProc("GetSystemMetrics")
2021-03-29 14:07:44 -04:00
getWindowDC = user32.NewProc("GetWindowDC")
releaseDC = user32.NewProc("ReleaseDC")
getDpiForWindow = user32.NewProc("GetDpiForWindow")
deleteObject = gdi32.NewProc("DeleteObject")
getDeviceCaps = gdi32.NewProc("GetDeviceCaps")
createFontIndirect = gdi32.NewProc("CreateFontIndirectW")
2018-01-25 10:37:06 -05:00
)
2021-03-11 10:37:56 -05:00
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexw
2021-03-26 09:26:56 -04:00
type _WNDCLASSEX struct {
2021-03-11 10:37:56 -05:00
Size uint32
Style uint32
WndProc uintptr
ClsExtra int32
WndExtra int32
Instance uintptr
Icon uintptr
Cursor uintptr
Background uintptr
MenuName *uint16
ClassName *uint16
IconSm uintptr
2018-01-25 10:37:06 -05:00
}
2021-03-26 09:26:56 -04:00
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-msg
2021-03-10 09:49:09 -05:00
type _MSG struct {
Owner syscall.Handle
Message uint32
WParam uintptr
LParam uintptr
Time uint32
Pt _POINT
2018-01-25 10:37:06 -05:00
}
2021-03-26 09:26:56 -04:00
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-nonclientmetricsw
type _NONCLIENTMETRICS struct {
Size uint32
BorderWidth int32
ScrollWidth int32
ScrollHeight int32
CaptionWidth int32
CaptionHeight int32
CaptionFont _LOGFONT
SmCaptionWidth int32
SmCaptionHeight int32
SmCaptionFont _LOGFONT
MenuWidth int32
MenuHeight int32
MenuFont _LOGFONT
StatusFont _LOGFONT
MessageFont _LOGFONT
2018-01-25 10:37:06 -05:00
}
2021-03-26 09:26:56 -04:00
// https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfontw
type _LOGFONT struct {
Height int32
Width int32
Escapement int32
Orientation int32
Weight int32
Italic byte
Underline byte
StrikeOut byte
CharSet byte
OutPrecision byte
ClipPrecision byte
Quality byte
PitchAndFamily byte
FaceName [32]uint16
2018-01-25 10:37:06 -05:00
}
2021-03-10 09:49:09 -05:00
// https://docs.microsoft.com/en-us/windows/win32/api/windef/ns-windef-point
type _POINT struct {
2018-01-25 10:37:06 -05:00
x, y int32
}
2021-03-10 09:49:09 -05:00
// https://docs.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
type _RECT struct {
2018-01-25 10:37:06 -05:00
left int32
top int32
right int32
bottom int32
}
2021-03-27 19:50:48 -04:00
type dpi uintptr
2021-03-29 14:07:44 -04:00
func getDPI(wnd uintptr) dpi {
var res uintptr
2021-03-27 19:50:48 -04:00
if wnd != 0 && getDpiForWindow.Find() == nil {
res, _, _ = getDpiForWindow.Call(wnd)
2021-03-29 14:07:44 -04:00
} else if dc, _, _ := getWindowDC.Call(wnd); dc != 0 {
2021-03-27 19:50:48 -04:00
res, _, _ = getDeviceCaps.Call(dc, 90) // LOGPIXELSY
2021-04-05 12:54:46 -04:00
releaseDC.Call(0, dc)
2021-03-27 19:50:48 -04:00
}
2021-03-29 14:07:44 -04:00
if res == 0 {
return 96 // USER_DEFAULT_SCREEN_DPI
2018-01-25 10:37:06 -05:00
}
2021-03-29 14:07:44 -04:00
return dpi(res)
2018-01-25 10:37:06 -05:00
}
2021-04-05 12:54:46 -04:00
func (d dpi) Scale(dim uintptr) uintptr {
if d == 0 {
return dim
2018-01-25 10:37:06 -05:00
}
2021-04-05 12:54:46 -04:00
return dim * uintptr(d) / 96 // USER_DEFAULT_SCREEN_DPI
2018-01-25 10:37:06 -05:00
}
2021-04-05 09:58:30 -04:00
type font struct {
handle uintptr
face _LOGFONT
}
2021-04-05 12:54:46 -04:00
func getFont() font {
2021-03-26 09:26:56 -04:00
var metrics _NONCLIENTMETRICS
metrics.Size = uint32(unsafe.Sizeof(metrics))
2021-04-05 09:58:30 -04:00
systemParametersInfo.Call(0x29, // SPI_GETNONCLIENTMETRICS
2021-03-29 14:07:44 -04:00
unsafe.Sizeof(metrics), uintptr(unsafe.Pointer(&metrics)), 0)
2021-04-05 09:58:30 -04:00
return font{face: metrics.MessageFont}
}
2021-04-05 12:54:46 -04:00
func (f *font) ForDPI(dpi dpi) uintptr {
2021-04-05 09:58:30 -04:00
if h := -int32(dpi.Scale(12)); f.handle == 0 || f.face.Height != h {
2021-04-05 12:54:46 -04:00
f.Delete()
2021-04-05 09:58:30 -04:00
f.face.Height = h
f.handle, _, _ = createFontIndirect.Call(uintptr(unsafe.Pointer(&f.face)))
}
return f.handle
2018-01-25 10:37:06 -05:00
}
2021-04-05 12:54:46 -04:00
func (f *font) Delete() {
2021-04-05 09:58:30 -04:00
if f.handle != 0 {
deleteObject.Call(f.handle)
f.handle = 0
}
}
2021-04-05 12:54:46 -04:00
func getWindowTextString(wnd uintptr) string {
len, _, _ := getWindowTextLength.Call(wnd)
buf := make([]uint16, len+1)
getWindowText.Call(wnd, uintptr(unsafe.Pointer(&buf[0])), len+1)
return syscall.UTF16ToString(buf)
}
func centerWindow(wnd uintptr) {
getMetric := func(i uintptr) int32 {
ret, _, _ := getSystemMetrics.Call(i)
return int32(ret)
}
var rect _RECT
getWindowRect.Call(wnd, uintptr(unsafe.Pointer(&rect)))
x := (getMetric(0 /* SM_CXSCREEN */) - (rect.right - rect.left)) / 2
y := (getMetric(1 /* SM_CYSCREEN */) - (rect.bottom - rect.top)) / 2
setWindowPos.Call(wnd, 0, uintptr(x), uintptr(y), 0, 0, 0x5) // SWP_NOZORDER|SWP_NOSIZE
}
func registerClass(instance, proc uintptr) (uintptr, error) {
2021-04-05 09:58:30 -04:00
name := "WC_" + strconv.FormatUint(uint64(proc), 16)
2021-03-26 09:26:56 -04:00
var wcx _WNDCLASSEX
2021-03-11 10:37:56 -05:00
wcx.Size = uint32(unsafe.Sizeof(wcx))
2021-04-05 09:58:30 -04:00
wcx.WndProc = proc
2021-03-29 14:07:44 -04:00
wcx.Instance = instance
wcx.Background = 5 // COLOR_WINDOW
2021-04-05 09:58:30 -04:00
wcx.ClassName = syscall.StringToUTF16Ptr(name)
2018-01-25 10:37:06 -05:00
2021-03-29 14:07:44 -04:00
ret, _, err := registerClassEx.Call(uintptr(unsafe.Pointer(&wcx)))
2021-04-05 12:54:46 -04:00
return ret, err
2018-01-25 10:37:06 -05:00
}
2021-03-10 09:49:09 -05:00
// https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues
2021-03-29 14:07:44 -04:00
func messageLoop(wnd uintptr) error {
2021-03-11 09:47:40 -05:00
getMessage := getMessage.Addr()
isDialogMessage := isDialogMessage.Addr()
translateMessage := translateMessage.Addr()
dispatchMessage := dispatchMessage.Addr()
2018-01-25 10:37:06 -05:00
for {
2021-03-27 19:50:48 -04:00
var msg _MSG
ret, _, err := syscall.Syscall6(getMessage, 4, uintptr(unsafe.Pointer(&msg)), 0, 0, 0, 0, 0)
2021-03-10 09:49:09 -05:00
if int32(ret) == -1 {
2018-01-25 10:37:06 -05:00
return err
}
2021-03-10 09:49:09 -05:00
if ret == 0 {
return nil
}
2018-01-25 10:37:06 -05:00
2021-03-29 14:07:44 -04:00
ret, _, _ = syscall.Syscall(isDialogMessage, 2, wnd, uintptr(unsafe.Pointer(&msg)), 0)
2021-03-10 09:49:09 -05:00
if ret == 0 {
2021-03-27 19:50:48 -04:00
syscall.Syscall(translateMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
syscall.Syscall(dispatchMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
2018-01-25 10:37:06 -05:00
}
}
}
2021-04-05 12:54:46 -04:00
func editBox(title, text string, opts options) (out string, ok bool, err error) {
2021-03-29 14:07:44 -04:00
var wnd, textCtl, editCtl uintptr
var okBtn, cancelBtn, extraBtn uintptr
defWindowProc := defWindowProc.Addr()
2021-04-05 12:54:46 -04:00
font := getFont()
defer font.Delete()
2021-04-05 09:58:30 -04:00
2021-03-29 14:07:44 -04:00
layout := func(dpi dpi) {
2021-04-05 12:54:46 -04:00
hfont := font.ForDPI(dpi)
2021-04-05 09:58:30 -04:00
sendMessage.Call(textCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
sendMessage.Call(editCtl, 0x0030 /* WM_SETFONT */, hfont, 1)
sendMessage.Call(okBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
sendMessage.Call(cancelBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
2021-04-05 12:54:46 -04:00
setWindowPos.Call(wnd, 0, 0, 0, dpi.Scale(281), dpi.Scale(140), 0x6) // SWP_NOZORDER|SWP_NOMOVE
setWindowPos.Call(textCtl, 0, dpi.Scale(12), dpi.Scale(10), dpi.Scale(241), dpi.Scale(16), 0x4) // SWP_NOZORDER
setWindowPos.Call(editCtl, 0, dpi.Scale(12), dpi.Scale(30), dpi.Scale(241), dpi.Scale(24), 0x4) // SWP_NOZORDER
if extraBtn == 0 {
setWindowPos.Call(okBtn, 0, dpi.Scale(95), dpi.Scale(65), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(65), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
} else {
sendMessage.Call(extraBtn, 0x0030 /* WM_SETFONT */, hfont, 1)
setWindowPos.Call(okBtn, 0, dpi.Scale(12), dpi.Scale(65), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
setWindowPos.Call(extraBtn, 0, dpi.Scale(95), dpi.Scale(65), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
setWindowPos.Call(cancelBtn, 0, dpi.Scale(178), dpi.Scale(65), dpi.Scale(75), dpi.Scale(24), 0x4) // SWP_NOZORDER
}
2018-01-25 10:37:06 -05:00
}
2021-03-29 14:07:44 -04:00
proc := func(wnd uintptr, msg uint32, wparam, lparam uintptr) uintptr {
2018-01-25 10:37:06 -05:00
switch msg {
2021-03-29 14:07:44 -04:00
case 0x0002: // WM_DESTROY
postQuitMessage.Call(0)
case 0x0010: // WM_CLOSE
destroyWindow.Call(wnd)
case 0x0111: // WM_COMMAND
switch wparam {
default:
return 1
case 1, 6: // IDOK, IDYES
2021-04-05 12:54:46 -04:00
out = getWindowTextString(editCtl)
2021-03-29 14:07:44 -04:00
ok = true
case 2: // IDCANCEL
case 7: // IDNO
2021-04-05 12:54:46 -04:00
err = ErrExtraButton
2018-01-25 10:37:06 -05:00
}
2021-03-29 14:07:44 -04:00
destroyWindow.Call(wnd)
case 0x02e0: // WM_DPICHANGED
layout(dpi(uint32(wparam) >> 16))
2018-01-25 10:37:06 -05:00
default:
2021-03-29 14:07:44 -04:00
ret, _, _ := syscall.Syscall6(defWindowProc, 4, wnd, uintptr(msg), wparam, lparam, 0, 0)
2018-01-25 10:37:06 -05:00
return ret
}
return 0
}
2021-03-27 19:50:48 -04:00
defer setup()()
2021-03-29 14:07:44 -04:00
instance, _, err := getModuleHandle.Call(0)
if instance == 0 {
return "", false, err
2018-01-25 10:37:06 -05:00
}
2021-04-05 12:54:46 -04:00
cls, err := registerClass(instance, syscall.NewCallback(proc))
if cls == 0 {
2021-03-29 14:07:44 -04:00
return "", false, err
2018-01-25 10:37:06 -05:00
}
2021-04-05 12:54:46 -04:00
defer unregisterClass.Call(cls, instance)
2018-01-25 10:37:06 -05:00
2021-04-05 12:54:46 -04:00
wnd, _, _ = createWindowEx.Call(0x10101, // WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE|WS_EX_DLGMODALFRAME
2021-04-05 13:58:32 -04:00
cls, strptr(title),
2021-04-05 09:58:30 -04:00
0x84c80000, // WS_POPUPWINDOW|WS_CLIPSIBLINGS|WS_DLGFRAME
0x80000000, // CW_USEDEFAULT
0x80000000, // CW_USEDEFAULT
281, 140, 0, 0, instance)
2018-01-25 10:37:06 -05:00
2021-04-05 12:54:46 -04:00
textCtl, _, _ = createWindowEx.Call(0,
2021-04-05 13:58:32 -04:00
strptr("STATIC"), strptr(text),
2021-04-05 09:58:30 -04:00
0x5002e080, // WS_CHILD|WS_VISIBLE|WS_GROUP|SS_WORDELLIPSIS|SS_EDITCONTROL|SS_NOPREFIX
12, 10, 241, 16, wnd, 0, instance)
2018-01-25 10:37:06 -05:00
2021-03-29 14:07:44 -04:00
var flags uintptr = 0x50030080 // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|ES_AUTOHSCROLL
2021-04-05 12:54:46 -04:00
if opts.hideText {
2021-03-29 14:07:44 -04:00
flags |= 0x20 // ES_PASSWORD
}
2021-04-05 12:54:46 -04:00
editCtl, _, _ = createWindowEx.Call(0x200, // WS_EX_CLIENTEDGE
2021-04-05 13:58:32 -04:00
strptr("EDIT"), strptr(opts.entryText),
2021-04-05 12:54:46 -04:00
flags,
2021-04-05 09:58:30 -04:00
12, 30, 241, 24, wnd, 0, instance)
2021-04-05 12:54:46 -04:00
okBtn, _, _ = createWindowEx.Call(0,
2021-04-05 13:58:32 -04:00
strptr("BUTTON"), strptr(*opts.okLabel),
2021-04-05 09:58:30 -04:00
0x50030001, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|BS_DEFPUSHBUTTON
12, 65, 75, 24, wnd, 1 /* IDOK */, instance)
2021-04-05 12:54:46 -04:00
cancelBtn, _, _ = createWindowEx.Call(0,
2021-04-05 13:58:32 -04:00
strptr("BUTTON"), strptr(*opts.cancelLabel),
2021-04-05 09:58:30 -04:00
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
2021-04-05 12:54:46 -04:00
12, 65, 75, 24, wnd, 2 /* IDCANCEL */, instance)
if opts.extraButton != nil {
extraBtn, _, _ = createWindowEx.Call(0,
2021-04-05 13:58:32 -04:00
strptr("BUTTON"), strptr(*opts.extraButton),
2021-04-05 12:54:46 -04:00
0x50010000, // WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP
12, 65, 75, 24, wnd, 7 /* IDNO */, instance)
}
2021-03-29 14:07:44 -04:00
layout(getDPI(wnd))
centerWindow(wnd)
setFocus.Call(editCtl)
showWindow.Call(wnd, 1 /* SW_SHOWNORMAL */, 0)
2021-04-05 13:58:32 -04:00
sendMessage.Call(editCtl, 0xb1 /* EM_SETSEL */, 0, intptr(-1))
2021-03-29 14:07:44 -04:00
2021-04-05 12:54:46 -04:00
err = nil
if err := messageLoop(wnd); err != nil {
2021-03-29 14:07:44 -04:00
return "", false, err
2018-01-25 10:37:06 -05:00
}
2021-04-05 12:54:46 -04:00
return out, ok, err
2018-01-25 10:37:06 -05:00
}