Finished writing initial Windows implementation. Now to find out why it doesn't work...

This commit is contained in:
Pietro Gagliardi 2014-07-12 11:29:54 -04:00
parent c55386f929
commit b944b6d4d8
6 changed files with 373 additions and 0 deletions

48
redo/common_windows.go Normal file
View File

@ -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)
}

View File

@ -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

View File

@ -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
}

98
redo/uitask_windows.go Normal file
View File

@ -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))
}

166
redo/window_windows.go Normal file
View File

@ -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}

View File

@ -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 {