summaryrefslogtreecommitdiff
path: root/uitask_windows.go
blob: 1566cb78fdc2a88240ddb59105ed620838b56174 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// 11 february 2014

package ui

import (
	"syscall"
	"unsafe"
	"runtime"
)

/*
problem: messages have to be dispatched on the same thread as system calls, and we can't mux GetMessage() with select, and PeekMessage() every iteration is wasteful (and leads to lag for me (only) with the concurrent garbage collector sweep)
solution: use PostThreadMessage() to send uimsgs out to the message loop, which runs on its own goroutine
I had come up with this first but wanted to try other things before doing it (and wasn't really sure if user-defined messages were safe, not quite understanding the system); nsf came up with it independently and explained that this was really the only right way to do it, so thanks to him
*/

var uitask chan *uimsg

type uimsg struct {
	call		*syscall.LazyProc
	p		[]uintptr
	ret		chan uiret
}

type uiret struct {
	ret		uintptr
	err		error
}

const (
	_WM_APP = 0x8000 + iota
	msgRequested
	msgQuit
	msgSetAreaSize
)

var (
	_getCurrentThreadID = kernel32.NewProc("GetCurrentThreadId")
	_postThreadMessage = user32.NewProc("PostThreadMessageW")
)

func ui(main func()) error {
	runtime.LockOSThread()

	uitask = make(chan *uimsg)
	err := doWindowsInit()
	if err != nil {
		return err
	}

	threadID, _, _ := _getCurrentThreadID.Call()

	go func() {
		for m := range uitask {
			r1, _, err := _postThreadMessage.Call(
				threadID,
				msgRequested,
				uintptr(0),
				uintptr(unsafe.Pointer(m)))
			if r1 == 0 {		// failure
				panic("error sending message to message loop to call function: " + err.Error())
			}
		}
	}()

	go func() {
		main()
		r1, _, err := _postThreadMessage.Call(
			threadID,
			msgQuit,
			uintptr(0),
			uintptr(0))
		if r1 == 0 {		// failure
			panic("error sending quit message to message loop: " + err.Error())
		}
	}()

	msgloop()

	doWindowsQuitStuff()
	return nil
}

var (
	_dispatchMessage = user32.NewProc("DispatchMessageW")
	_getMessage = user32.NewProc("GetMessageW")
	_postQuitMessage = user32.NewProc("PostQuitMessage")
	_sendMessage = user32.NewProc("SendMessageW")
	_translateMessage = user32.NewProc("TranslateMessage")
)

var getMessageFail = -1		// because Go doesn't let me

func msgloop() {
	var msg struct {
		Hwnd	_HWND
		Message	uint32
		WParam	_WPARAM
		LParam	_LPARAM
		Time		uint32
		Pt		_POINT
	}

	for {
		r1, _, err := _getMessage.Call(
			uintptr(unsafe.Pointer(&msg)),
			uintptr(_NULL),
			uintptr(0),
			uintptr(0))
		if r1 == uintptr(getMessageFail) {		// error
			panic("error getting message in message loop: " + err.Error())
		}
		if r1 == 0 {		// WM_QUIT message
			return
		}
		if msg.Message == msgRequested {
			m := (*uimsg)(unsafe.Pointer(msg.LParam))
			r1, _, err := m.call.Call(m.p...)
			m.ret <- uiret{
				ret:	r1,
				err:	err,
			}
			continue
		}
		if msg.Message == msgQuit {
			// does not return a value according to MSDN
			_postQuitMessage.Call(0)
			continue
		}
		_translateMessage.Call(uintptr(unsafe.Pointer(&msg)))
		_dispatchMessage.Call(uintptr(unsafe.Pointer(&msg)))
	}
}