From 3482c9c541210dbf0e7bdd5dbf2c6372d5d55162 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 9 Feb 2014 14:59:37 -0500 Subject: [PATCH] Added the rest of the skeleton necessary for opening a simple window as well as the code to actually open one. Now for custom window procedures! --- main.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++---- messages.go | 49 ++++++++++++++++++++++++++ painting.go | 20 +++++++++++ rectangles.go | 14 ++++++++ windows.go | 64 +++++++++++++++++++++++++++++++++- wndclass.go | 4 +-- 6 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 messages.go create mode 100644 painting.go create mode 100644 rectangles.go diff --git a/main.go b/main.go index c8162ef..b16378e 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,95 @@ // 7 february 2014 package main -import "fmt" +import ( + "fmt" + "os" + "runtime" +) -func main() { - fmt.Println(MessageBox(NULL, - "hello, world", - "hello", - 0)) +func fatalf(format string, args ...interface{}) { + s := fmt.Sprintf(format, args...) + _, err := MessageBox(NULL, + "An internal error has occured:\n" + s, + os.Args[0], + MB_OK | MB_ICONERROR) + if err == nil { + os.Exit(1) + } + panic(fmt.Sprintf("error trying to warn user of internal error: %v\ninternal error:\n%s", err, s)) +} + +const className = "mainwin" + +func main() { + runtime.LockOSThread() + + hInstance, err := getWinMainhInstance() + if err != nil { + fatalf("error getting WinMain hInstance: %v", err) + } + nCmdShow, err := getWinMainnCmdShow() + if err != nil { + fatalf("error getting WinMain nCmdShow: %v", err) + } + + icon, err := LoadIcon_ResourceID(NULL, IDI_APPLICATION) + if err != nil { + fatalf("error getting window icon: %v", err) + } + cursor, err := LoadCursor_ResourceID(NULL, IDC_ARROW) + if err != nil { + fatalf("error getting window cursor: %v", err) + } + + wc := &WNDCLASS{ + LpszClassName: className, + LpfnWndProc: DefWindowProc, + HInstance: hInstance, + HIcon: icon, + HCursor: cursor, + HbrBackground: HBRUSH(COLOR_WINDOW + 1), + } + _, err = RegisterClass(wc) + if err != nil { + fatalf("error registering window class: %v", err) + } + + hwnd, err := CreateWindowEx( + WS_EX_OVERLAPPEDWINDOW, + className, "Main Window", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, + NULL, NULL, hInstance, NULL) + if err != nil { + fatalf("error creating window: %v", err) + } + + _, err = ShowWindow(hwnd, nCmdShow) + if err != nil { + fatalf("error showing window: %v", err) + } + err = UpdateWindow(hwnd) + if err != nil { + fatalf("error updating window: %v", err) + } + + for { + msg, done, err := GetMessage(NULL, 0, 0) + if err != nil { + fatalf("error getting message: %v", err) + } + if done { + break + } + _, err = TranslateMessage(msg) + if err != nil { + fatalf("error translating message: %v", err) + } + _, err = DispatchMessage(msg) + if err != nil { + fatalf("error dispatching message: %v", err) + } + } } diff --git a/messages.go b/messages.go new file mode 100644 index 0000000..aa01a7e --- /dev/null +++ b/messages.go @@ -0,0 +1,49 @@ +// 9 february 2014 +package main + +import ( +// "syscall" + "unsafe" +) + +type MSG struct { + Hwnd HWND + Message uint32 + WParam WPARAM + LParam LPARAM + Time uint32 + Pt POINT +} + +var ( + dispatchMessage = user32.NewProc("DispatchMessageW") + getMessage = user32.NewProc("GetMessageW") + translateMessage = user32.NewProc("TranslateMessage") +) + +// TODO handle errors +func DispatchMessage(lpmsg *MSG) (result LRESULT, err error) { + r1, _, _ := dispatchMessage.Call(uintptr(unsafe.Pointer(lpmsg))) + return LRESULT(r1), nil +} + +var getMessageFail = -1 // because Go doesn't let me + +func GetMessage(hWnd HWND, wMsgFilterMin uint32, wMsgFilterMax uint32) (lpMsg *MSG, quit bool, err error) { + lpMsg = new(MSG) + r1, _, err := getMessage.Call( + uintptr(unsafe.Pointer(lpMsg)), + uintptr(hWnd), + uintptr(wMsgFilterMin), + uintptr(wMsgFilterMax)) + if r1 == uintptr(getMessageFail) { // failure + return nil, false, err + } + return lpMsg, r1 == 0, nil +} + +// TODO handle errors +func TranslateMessage(lpMsg *MSG) (translated bool, err error) { + r1, _, _ := translateMessage.Call(uintptr(unsafe.Pointer(lpMsg))) + return r1 != 0, nil +} diff --git a/painting.go b/painting.go new file mode 100644 index 0000000..069fd2d --- /dev/null +++ b/painting.go @@ -0,0 +1,20 @@ +// 9 february 2014 +package main + +import ( +// "syscall" +// "unsafe" +) + +var ( + updateWindow = user32.NewProc("UpdateWindow") +) + +// TODO is error handling valid here? MSDN just says zero on failure; syscall.LazyProc.Call() always returns non-nil +func UpdateWindow(hWnd HWND) (err error) { + r1, _, err := updateWindow.Call(uintptr(hWnd)) + if r1 == 0 { // failure + return err + } + return nil +} diff --git a/rectangles.go b/rectangles.go new file mode 100644 index 0000000..d503edc --- /dev/null +++ b/rectangles.go @@ -0,0 +1,14 @@ +// 9 february 2014 +package main + +import ( +// "syscall" +// "unsafe" +) + +// TODO merge with common.go? + +type POINT struct { + X int32 + Y int32 +} diff --git a/windows.go b/windows.go index 5cfa53d..bf840fb 100644 --- a/windows.go +++ b/windows.go @@ -6,6 +6,37 @@ import ( "unsafe" ) +// Window styles. +const ( + WS_BORDER = 0x00800000 + WS_CAPTION = 0x00C00000 + WS_CHILD = 0x40000000 + WS_CHILDWINDOW = 0x40000000 + WS_CLIPCHILDREN = 0x02000000 + WS_CLIPSIBLINGS = 0x04000000 + WS_DISABLED = 0x08000000 + WS_DLGFRAME = 0x00400000 + WS_GROUP = 0x00020000 + WS_HSCROLL = 0x00100000 + WS_ICONIC = 0x20000000 + WS_MAXIMIZE = 0x01000000 + WS_MAXIMIZEBOX = 0x00010000 + WS_MINIMIZE = 0x20000000 + WS_MINIMIZEBOX = 0x00020000 + WS_OVERLAPPED = 0x00000000 + WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) + WS_POPUP = 0x80000000 + WS_POPUPWINDOW = (WS_POPUP | WS_BORDER | WS_SYSMENU) + WS_SIZEBOX = 0x00040000 + WS_SYSMENU = 0x00080000 + WS_TABSTOP = 0x00010000 + WS_THICKFRAME = 0x00040000 + WS_TILED = 0x00000000 + WS_TILEDWINDOW = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) + WS_VISIBLE = 0x10000000 + WS_VSCROLL = 0x00200000 +) + // Extended window styles. const ( WS_EX_ACCEPTFILES = 0x00000010 @@ -36,7 +67,12 @@ const ( WS_EX_WINDOWEDGE = 0x00000100 ) -// TODO CW_USEDEFAULT +// bizarrely, this value is given on the page for CreateMDIWindow, but not CreateWindow or CreateWindowEx +// I do it this way because Go won't let me shove the exact value into an int +var ( + _uCW_USEDEFAULT uint = 0x80000000 + CW_USEDEFAULT = int(_uCW_USEDEFAULT) +) // GetSysColor values. These can be cast to HBRUSH (after adding 1) for WNDCLASS as well. const ( @@ -78,8 +114,26 @@ const ( COLOR_WINDOWTEXT = 8 ) +// ShowWindow settings. +const ( + SW_FORCEMINIMIZE = 11 + SW_HIDE = 0 + SW_MAXIMIZE = 3 + SW_MINIMIZE = 6 + SW_RESTORE = 9 + SW_SHOW = 5 + SW_SHOWDEFAULT = 10 + SW_SHOWMAXIMIZED = 3 + SW_SHOWMINIMIZED = 2 + SW_SHOWMINNOACTIVE = 7 + SW_SHOWNA = 8 + SW_SHOWNOACTIVATE = 4 + SW_SHOWNORMAL = 1 +) + var ( createWindowEx = user32.NewProc("CreateWindowExW") + showWindow = user32.NewProc("ShowWindow") ) // TODO use lpParam @@ -102,3 +156,11 @@ func CreateWindowEx(dwExStyle uint32, lpClassName string, lpWindowName string, d } return HWND(r1), nil } + +// TODO figure out how to handle errors +func ShowWindow(hWnd HWND, nCmdShow int) (previouslyVisible bool, err error) { + r1, _, _ := showWindow.Call( + uintptr(hWnd), + uintptr(nCmdShow)) + return r1 != 0, nil +} diff --git a/wndclass.go b/wndclass.go index 7296369..38ff4ae 100644 --- a/wndclass.go +++ b/wndclass.go @@ -21,7 +21,7 @@ type WNDCLASS struct { type _WNDCLASSW struct { style uint32 - lpfnWndProc WNDPROC + lpfnWndProc uintptr cbClsExtra int cbWndExtra int hInstance HANDLE @@ -39,7 +39,7 @@ func (w *WNDCLASS) toNative() *_WNDCLASSW { } return &_WNDCLASSW{ style: w.Style, - lpfnWndProc: w.LpfnWndProc, + lpfnWndProc: syscall.NewCallback(w.LpfnWndProc), cbClsExtra: w.CbClsExtra, cbWndExtra: w.CbWndExtra, hInstance: w.HInstance,