From b944b6d4d83115dfcbb468e01d6a2cdf34b3078e Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 12 Jul 2014 11:29:54 -0400 Subject: [PATCH] Finished writing initial Windows implementation. Now to find out why it doesn't work... --- redo/common_windows.go | 48 +++++++++++ redo/funcnames_windows.go | 21 +++++ redo/init_windows.go | 18 +++++ redo/uitask_windows.go | 98 ++++++++++++++++++++++ redo/window_windows.go | 166 ++++++++++++++++++++++++++++++++++++++ redo/zwinconstgen.go | 22 +++++ 6 files changed, 373 insertions(+) create mode 100644 redo/common_windows.go create mode 100644 redo/uitask_windows.go create mode 100644 redo/window_windows.go diff --git a/redo/common_windows.go b/redo/common_windows.go new file mode 100644 index 0000000..d2cae60 --- /dev/null +++ b/redo/common_windows.go @@ -0,0 +1,48 @@ +// 12 july 2014 + +package ui + +import ( + "fmt" + "syscall" + "unsafe" +) + +// TODO get rid of this when we actually use s_POINT somewhere +var dummyToFoolwinconstgen s_POINT + +func getWindowText(hwnd uintptr) string { + // WM_GETTEXTLENGTH and WM_GETTEXT return the count /without/ the terminating null character + // but WM_GETTEXT expects the buffer size handed to it to /include/ the terminating null character + n := f_SendMessageW(hwnd, c_WM_GETTEXTLENGTH, 0, 0) + buf := make([]uint16, int(n + 1)) + if f_SendMessageW(hwnd, c_WM_GETTEXT, + t_WPARAM(n + 1), t_LPARAM(uintptr(unsafe.Pointer(&buf[0])))) != n { + panic(fmt.Errorf("WM_GETTEXT did not copy exactly %d characters out", n)) + } + return syscall.UTF16ToString(buf) +} + +func setWindowText(hwnd uintptr, text string, errors []t_LRESULT) { + res := f_SendMessageW(hwnd, c_WM_SETTEXT, + 0, t_LPARAM(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))))) + for _, err := range errors { + if res == err { + panic(fmt.Errorf("WM_SETTEXT failed; error code %d", res)) + } + } +} + +func updateWindow(hwnd uintptr, caller string) { + res, err := f_UpdateWindow(hwnd) + if res == 0 { + panic(fmt.Errorf("error calling UpdateWindow() from %s: %v", caller, err)) + } +} + +func storelpParam(hwnd uintptr, lParam t_LPARAM) { + var cs *s_CREATESTRUCTW + + cs = (*s_CREATESTRUCTW)(unsafe.Pointer(uintptr(lParam))) + f_SetWindowLongPtrW(hwnd, c_GWLP_USERDATA, cs.lpCreateParams) +} diff --git a/redo/funcnames_windows.go b/redo/funcnames_windows.go index 02b85ca..ce334f2 100644 --- a/redo/funcnames_windows.go +++ b/redo/funcnames_windows.go @@ -4,3 +4,24 @@ package ui // wfunc kernel32 GetModuleHandleW *uint16 uintptr // wfunc kernel32 GetStartupInfoW *s_STARTUPINFOW void +// wfunc user32 LoadIconW uintptr uintptr uintptr +// wfunc user32 LoadCursorW uintptr uintptr uintptr +// wfunc user32 GetMessageW *s_MSG uintptr t_UINT t_UINT t_BOOL + +// these two don't technically return void but let's pretend they do since their return values are irrelevant/not indicative of anything useful +// wfunc user32 TranslateMessage *s_MSG void +// wfunc user32 DispatchMessageW *s_MSG void + +// wfunc user32 PostMessageW uintptr t_UINT t_WPARAM t_LPARAM uintptr +// wfunc user32 RegisterClassW *s_WNDCLASSW uintptr + +// TODO narrow down argument types +// wfunc user32 CreateWindowExW uintptr *uint16 *uint16 uintptr uintptr uintptr uintptr uintptr uintptr uintptr uintptr unsafe.Pointer uintptr + +// wfunc user32 DefWindowProcW uintptr t_UINT t_WPARAM t_LPARAM t_LRESULT,noerr + +// this one doesn't technically return void but let's pretend it does since its return value is irrelevant/not indicative of anything useful +// wfunc user32 ShowWindow uintptr uintptr void + +// wfunc user32 SendMessageW uintptr t_UINT t_WPARAM t_LPARAM t_LRESULT,noerr +// wfunc user32 UpdateWindow uintptr uintptr diff --git a/redo/init_windows.go b/redo/init_windows.go index 4375cee..1ce0e65 100644 --- a/redo/init_windows.go +++ b/redo/init_windows.go @@ -32,9 +32,27 @@ func getWinMainParams() (err error) { return nil } +// TODO move to common_windows.go +var hNULL uintptr = 0 + +func loadIconsCursors() (err error) { + hDefaultIcon, err = f_LoadIconW(hNULL, c_IDI_APPLICATION) + if hDefaultIcon == hNULL { + return fmt.Errorf("error loading default icon: %v", err) + } + hArrowCursor, err = f_LoadCursorW(hNULL, c_IDC_ARROW) + if hArrowCursor == hNULL { + return fmt.Errorf("error loading arrow (default) cursor: %v", err) + } + return nil +} + func initWindows() error { if err := getWinMainParams(); err != nil { return fmt.Errorf("error getting WinMain() parameters: %v", err) } + if err := loadIconsCursors(); err != nil { + return fmt.Errorf("error loading standard/default icons and cursors: %v", err) + } return nil } diff --git a/redo/uitask_windows.go b/redo/uitask_windows.go new file mode 100644 index 0000000..0c43f36 --- /dev/null +++ b/redo/uitask_windows.go @@ -0,0 +1,98 @@ +// 12 july 2014 + +package ui + +import ( + "fmt" + "syscall" + "unsafe" +) + +// global messages unique to everything +const ( + msgRequest = c_WM_APP + 1 + iota // + 1 just to be safe +) + +var msgwin uintptr + +func uiinit() error { + if err := initWindows(); err != nil { + return fmt.Errorf("error initializing package ui on Windows: %v", err) + } + if err := makemsgwin(); err != nil { + return fmt.Errorf("error creating message-only window: %v", err) + } + if err := makeWindowWindowClass(); err != nil { + return fmt.Errorf("error creating Window window class: %v", err) + } + return nil +} + +func uimsgloop() { + var msg s_MSG + + for { + res, err := f_GetMessageW(&msg, hNULL, 0, 0) + if res < 0 { + panic(fmt.Errorf("error calling GetMessage(): %v", err)) + } + if res == 0 { // WM_QUIT + break + } + // TODO IsDialogMessage() + f_TranslateMessage(&msg) + f_DispatchMessageW(&msg) + } +} + +func issue(req *Request) { + res, err := f_PostMessageW( + hNULL, // TODO + msgRequest, + 0, + t_LPARAM(uintptr(unsafe.Pointer(req)))) + if res == 0 { + panic(fmt.Errorf("error issuing request: %v", err)) + } +} + +const msgwinclass = "gouimsgwin" + +func makemsgwin() error { + var wc s_WNDCLASSW + + wc.lpfnWndProc = syscall.NewCallback(msgwinproc) + wc.hInstance = hInstance + wc.hIcon = hDefaultIcon + wc.hCursor = hArrowCursor + wc.hbrBackground = c_COLOR_BTNFACE + 1 + wc.lpszClassName = syscall.StringToUTF16Ptr(msgwinclass) + res, err := f_RegisterClassW(&wc) + if res == 0 { + return fmt.Errorf("error registering message-only window class: %v", err) + } + msgwin, err = f_CreateWindowExW( + 0, + wc.lpszClassName, + syscall.StringToUTF16Ptr("package ui message-only window"), + 0, + c_CW_USEDEFAULT, c_CW_USEDEFAULT, + c_CW_USEDEFAULT, c_CW_USEDEFAULT, + c_HWND_MESSAGE, hNULL, hInstance, nil) + if msgwin == hNULL { + return fmt.Errorf("error creating message-only window: %v", err) + } + return nil +} + +func msgwinproc(hwnd uintptr, uMsg t_UINT, wParam t_WPARAM, lParam t_LPARAM) t_LRESULT { + switch uMsg { + case msgRequest: + req := (*Request)(unsafe.Pointer(uintptr(lParam))) + perform(req) + return 0 + default: + return f_DefWindowProcW(hwnd, uMsg, wParam, lParam) + } + panic(fmt.Errorf("message-only window procedure does not return a value for message %d (bug in msgwinproc())", uMsg)) +} diff --git a/redo/window_windows.go b/redo/window_windows.go new file mode 100644 index 0000000..aba3232 --- /dev/null +++ b/redo/window_windows.go @@ -0,0 +1,166 @@ +// 12 july 2014 + +package ui + +import ( + "fmt" + "syscall" + "unsafe" +) + +type window struct { + hwnd uintptr + shownbefore bool + + closing *event +} + +const windowclassname = "gouiwindow" +var windowclassptr = syscall.StringToUTF16Ptr(windowclassname) + +func makeWindowWindowClass() error { + var wc s_WNDCLASSW + + wc.lpfnWndProc = syscall.NewCallback(windowWndProc) + wc.hInstance = hInstance + wc.hIcon = hDefaultIcon + wc.hCursor = hArrowCursor + wc.hbrBackground = c_COLOR_BTNFACE + 1 + wc.lpszClassName = windowclassptr + res, err := f_RegisterClassW(&wc) + if res == 0 { + return fmt.Errorf("error registering Window window class: %v", err) + } + return nil +} + +func newWindow(title string, width int, height int) *Request { + c := make(chan interface{}) + return &Request{ + op: func() { + w := &window{ + // hwnd set in WM_CREATE handler + closing: newEvent(), + } + hwnd, err := f_CreateWindowExW( + 0, + windowclassptr, + syscall.StringToUTF16Ptr(title), + c_WS_OVERLAPPED, + c_CW_USEDEFAULT, c_CW_USEDEFAULT, + uintptr(width), uintptr(height), + hNULL, hNULL, hInstance, unsafe.Pointer(w)) + if hwnd == hNULL { + panic(fmt.Errorf("Window creation failed: %v", err)) + } else if hwnd != w.hwnd { + panic(fmt.Errorf("inconsistency: hwnd returned by CreateWindowEx() (%p) and hwnd stored in window (%p) differ", hwnd, w.hwnd)) + } + c <- w + }, + resp: c, + } +} + +func (w *window) SetControl(control Control) *Request { + c := make(chan interface{}) + return &Request{ + op: func() { + // TODO unparent + // TODO reparent + c <- struct{}{} + }, + resp: c, + } +} + +func (w *window) Title() *Request { + c := make(chan interface{}) + return &Request{ + op: func() { + c <- getWindowText(w.hwnd) + }, + resp: c, + } +} + +func (w *window) SetTitle(title string) *Request { + c := make(chan interface{}) + return &Request{ + op: func() { + setWindowText(w.hwnd, title, []t_LRESULT{c_FALSE}) + c <- struct{}{} + }, + resp: c, + } +} + +func (w *window) Show() *Request { + c := make(chan interface{}) + return &Request{ + op: func() { + if !w.shownbefore { + // TODO get rid of need for cast + f_ShowWindow(w.hwnd, uintptr(nCmdShow)) + updateWindow(w.hwnd, "Window.Show()") + w.shownbefore = true + } else { + f_ShowWindow(w.hwnd, c_SW_SHOW) + } + c <- struct{}{} + }, + resp: c, + } +} + +func (w *window) Hide() *Request { + c := make(chan interface{}) + return &Request{ + op: func() { + f_ShowWindow(w.hwnd, c_SW_HIDE) + c <- struct{}{} + }, + resp: c, + } +} + +func (w *window) Close() *Request { + c := make(chan interface{}) + return &Request{ + op: func() { + // TODO + c <- struct{}{} + }, + resp: c, + } +} + +func (w *window) OnClosing(e func(Doer) bool) *Request { + c := make(chan interface{}) + return &Request{ + op: func() { + w.closing.setbool(e) + c <- struct{}{} + }, + resp: c, + } +} + +func windowWndProc(hwnd uintptr, msg t_UINT, wParam t_WPARAM, lParam t_LPARAM) t_LRESULT { + w := (*window)(unsafe.Pointer(f_GetWindowLongPtrW(hwnd, c_GWLP_USERDATA))) + if w == nil { + // the lpParam is available during WM_NCCREATE and WM_CREATE + if msg == c_WM_NCCREATE { + storelpParam(hwnd, lParam) + } + // act as if we're not ready yet, even during WM_NCCREATE (nothing important to the switch statement below happens here anyway) + return f_DefWindowProcW(hwnd, msg, wParam, lParam) + } + switch msg { + default: + return f_DefWindowProcW(hwnd, msg, wParam, lParam) + } + panic(fmt.Errorf("Window message %d does not return a value (bug in windowWndProc())", msg)) +} + +// TODO +func newButton(string)*Request{return nil} \ No newline at end of file diff --git a/redo/zwinconstgen.go b/redo/zwinconstgen.go index 07c5959..28ff843 100644 --- a/redo/zwinconstgen.go +++ b/redo/zwinconstgen.go @@ -172,6 +172,22 @@ var gwlpNames = map[string]string{ "amd64": "etWindowLongPtrW", } +// in reality these use LONG_PTR for the actual values; LONG_PTR is a signed value, but for our use case it doesn't really matter +func genGetSetWindowLongPtr(targetarch string) { + name := gwlpNames[targetarch] + + funcs = append(funcs, fmt.Sprintf("var fv_GetWindowLongPtrW = user32.NewProc(%q)", "G" + name)) + funcs = append(funcs, "func f_GetWindowLongPtrW(hwnd uintptr, which uintptr) uintptr {") + funcs = append(funcs, "\tres, _, _ := fv_GetWindowLongPtrW.Call(hwnd, which)") + funcs = append(funcs, "\treturn res") + funcs = append(funcs, "}") + + funcs = append(funcs, fmt.Sprintf("var fv_SetWindowLongPtrW = user32.NewProc(%q)", "S" + name)) + funcs = append(funcs, "func f_SetWindowLongPtrW(hwnd uintptr, which uintptr, value uintptr) {") + funcs = append(funcs, "\tfv_SetWindowLongPtrW.Call(hwnd, which, value)") + funcs = append(funcs, "}") +} + const outTemplate = `package main import ( "fmt" @@ -202,6 +218,7 @@ var handleOverrides = []string{ "HICON", "HCURSOR", "HBRUSH", + "HMENU", // These are all pointers to functions; handle them identically to handles. "WNDPROC", } @@ -259,6 +276,8 @@ func main() { fmt.Fprintf(buf, "type t_WPARAM %s\n", winName(reflect.TypeOf(C.WPARAM(0)))) fmt.Fprintf(buf, "type t_LPARAM %s\n", winName(reflect.TypeOf(C.LPARAM(0)))) fmt.Fprintf(buf, "type t_LRESULT %s\n", winName(reflect.TypeOf(C.LRESULT(0)))) + // and one for GetMessageW() + fmt.Fprintf(buf, "type t_BOOL %s\n", winName(reflect.TypeOf(C.BOOL(0)))) // functions {{range .Funcs}} fmt.Fprintf(buf, "%s\n", {{printf "%q" .}}) @@ -332,6 +351,9 @@ func main() { sort.Strings(structs) sort.Strings(sorteddlls) + // and finally + genGetSetWindowLongPtr(targetarch) + // thanks to james4k in irc.freenode.net/#go-nuts tmpdir, err := ioutil.TempDir("", "windowsconstgen") if err != nil {