Finished writing initial Windows implementation. Now to find out why it doesn't work...
This commit is contained in:
parent
c55386f929
commit
b944b6d4d8
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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}
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue