-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathwin32.py
306 lines (236 loc) · 8.67 KB
/
win32.py
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# -*- coding: utf-8 -*-
"""
Minimalistic wrapper over the Windows window api
"""
import vk
from ctypes import *
from ctypes.wintypes import *
import asyncio, weakref
### BINDINGS ###
# Extern libraries
k32 = windll.kernel32
u32 = windll.user32
# TYPES
LRESULT = c_size_t
HCURSOR = HICON
WNDPROC = WINFUNCTYPE(LRESULT, HWND, UINT, WPARAM, LPARAM)
# Consts
CS_HREDRAW = 0x0002
CS_VREDRAW = 0x0001
CS_OWNDC = 0x0020
IDC_ARROW = LPCWSTR(32512)
WM_CREATE = 0x0001
WM_CLOSE = 0x0010
WM_QUIT = 0x0012
WM_MOUSEWHEEL = 0x020A
WM_RBUTTONDOWN = 0x0204
WM_LBUTTONDOWN = 0x0201
WM_MOUSEMOVE = 0x0200
WM_SIZE = 0x0005
WM_EXITSIZEMOVE = 0x0232
MK_RBUTTON = 0x0002
MK_LBUTTON = 0x0001
WS_CLIPCHILDREN = 0x02000000
WS_CLIPSIBLINGS = 0x04000000
WS_OVERLAPPED = 0x00000000
WS_CAPTION = 0x00C00000
WS_SYSMENU = 0x00080000
WS_THICKFRAME = 0x00040000
WS_MINIMIZEBOX = 0x00020000
WS_MAXIMIZEBOX = 0x00010000
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
SIZE_MAXIMIZED = 2
SIZE_RESTORED = 0
CW_USEDEFAULT = 0x80000000
SW_SHOWNORMAL = 5
PM_REMOVE = 0x0001
NULL = c_void_p(0)
NULL_WSTR = LPCWSTR(0)
# Structures
class WNDCLASSEXW(Structure):
_fields_ = (('cbSize', UINT), ('style', UINT), ('lpfnWndProc', WNDPROC), ('cbClsExtra', c_int),
('cbWndExtra', c_int), ('hinstance', HINSTANCE), ('hIcon', HICON), ('hCursor', HCURSOR),
('hbrBackground', HBRUSH), ('lpszMenuName', LPCWSTR), ('lpszClassName', LPCWSTR), ('hIconSm', HICON))
# Functions
def result_not_null(msg):
def inner(value):
if value == 0:
raise WindowsError(msg + '\n' + FormatError())
return value
return inner
GetModuleHandleW = k32.GetModuleHandleW
GetModuleHandleW.restype = result_not_null('Failed to get window module')
GetModuleHandleW.argtypes = (LPCSTR,)
LoadCursorW = u32.LoadCursorW
LoadCursorW.restype = result_not_null('Failed to load cursor')
LoadCursorW.argtypes = (HINSTANCE, LPCWSTR)
RegisterClassExW = u32.RegisterClassExW
RegisterClassExW.restype = result_not_null('Failed to register class')
RegisterClassExW.argtypes = (POINTER(WNDCLASSEXW),)
DefWindowProcW = u32.DefWindowProcW
DefWindowProcW.restype = LPARAM
DefWindowProcW.argtypes = (HWND, UINT, WPARAM, LPARAM)
CreateWindowExW = u32.CreateWindowExW
CreateWindowExW.restype = result_not_null('Failed to create window')
CreateWindowExW.argtypes = (DWORD, LPCWSTR, LPCWSTR, DWORD, c_int, c_int, c_int, c_int, HWND, HMENU, HINSTANCE, c_void_p)
UnregisterClassW = u32.UnregisterClassW
UnregisterClassW.restype = result_not_null('Failed to unregister class')
UnregisterClassW.argtypes = (LPCWSTR, HINSTANCE)
DestroyWindow = u32.DestroyWindow
DestroyWindow.restype = HWND
DestroyWindow.argtypes = (HWND,)
ShowWindow = u32.ShowWindow
ShowWindow.restype = BOOL
ShowWindow.argtypes = (HWND, c_int)
PeekMessageW = u32.PeekMessageW
PeekMessageW.restype = BOOL
PeekMessageW.argtypes = (POINTER(MSG), HWND, UINT, UINT, UINT)
DispatchMessageW = u32.DispatchMessageW
DispatchMessageW.restype = LRESULT
DispatchMessageW.argtypes = (POINTER(MSG),)
TranslateMessage = u32.TranslateMessage
TranslateMessage.restype = BOOL
TranslateMessage.argtypes = (POINTER(MSG),)
PostQuitMessage = u32.PostQuitMessage
PostQuitMessage.restype = None
PostQuitMessage.argtypes = (c_int,)
GetClientRect = u32.GetClientRect
GetClientRect.restype = result_not_null('Failed to get window dimensions')
GetClientRect.argtypes = (HWND, POINTER(RECT))
SetWindowTextW = u32.SetWindowTextW
SetWindowTextW.restype = result_not_null('Failed to set the window name')
SetWindowTextW.argtypes = (HWND, LPCWSTR)
################
mouse_pos = (0, 0)
resize_target = (0, 0)
def wndproc(window, hwnd, msg, w, l):
global mouse_pos, resize_target
if msg == WM_MOUSEMOVE:
app = window.app()
x, y = float(c_short(l).value), float(c_short(l>>16).value)
if w & MK_RBUTTON:
app.zoom += (mouse_pos[1] - float(y)) * 0.005
elif w & MK_LBUTTON:
app.rotation[0] += (mouse_pos[1] - float(y)) * 1.25
app.rotation[1] += (mouse_pos[0] - float(x)) * 1.25
mouse_pos = (x,y)
app.update_uniform_buffers()
elif msg in (WM_RBUTTONDOWN, WM_LBUTTONDOWN):
x, y = float(c_short(l).value), float(c_short(l>>16).value)
mouse_pos = (x,y)
elif msg == WM_MOUSEWHEEL:
app = window.app()
wheel_delta = float(c_short(w>>16).value)
app.zoom += wheel_delta*0.002
app.update_uniform_buffers()
if msg == WM_SIZE:
resize_target = c_short(l).value, c_short(l>>16).value
if w in (SIZE_MAXIMIZED, SIZE_RESTORED):
window.app().resize_display(*resize_target)
elif msg == WM_EXITSIZEMOVE:
window.app().resize_display(*resize_target)
if msg == WM_CREATE:
pass
elif msg == WM_CLOSE:
DestroyWindow(hwnd)
window.__hwnd = None
window.app().running = False # Stop the rendering loop of the app
PostQuitMessage(0)
else:
return DefWindowProcW(hwnd, msg, w, l)
return 0
async def process_events(app):
"""
Dispatch the user/system events to the window
"""
listen_events = True
while listen_events:
msg = MSG()
while PeekMessageW(byref(msg), NULL, 0, 0, PM_REMOVE) != 0:
listen_events = msg.message != WM_QUIT
TranslateMessage(byref(msg))
DispatchMessageW(byref(msg))
await asyncio.sleep(1/30)
# Wait til the app rendering is done before exiting the loop
# Otherwise, the loop owns a ref of the app and therefore, the app cannot be freed.
app = app()
if app is not None:
await app.rendering_done.wait()
asyncio.get_event_loop().stop()
class Win32Window(object):
def __init__(self, app):
# Wrapper over the Windows window procedure. This allows the window object to be sent
# to the wndproc in a simple and safe way.
# Also, I like to use the double underscore notation to define system specific fields
# This way, when debugging they appear like this: '_Win32Window__wndproc'
self.__wndproc = WNDPROC(lambda hwnd, msg, w, l: wndproc(self, hwnd, msg, w, l))
self.__class_name = "VULKAN_TEST_"+str(id(self))
self.__hwnd = None
self.app = weakref.ref(app)
mod = GetModuleHandleW(None)
# Register the window class
class_def = WNDCLASSEXW(
cbSize = sizeof(WNDCLASSEXW),
lpfnWndProc = self.__wndproc,
style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
cbClsExtra = 0, cbWndExtra = 0,
hInstance = mod, hIcon = NULL,
hCursor = LoadCursorW(NULL, IDC_ARROW),
hbrBackground = NULL,
lpszMenuName = NULL_WSTR,
lpszClassName = self.__class_name,
hIconSm = NULL
)
RegisterClassExW(byref(class_def))
# Create the window
self.__hwnd = CreateWindowExW(
0, self.__class_name,
"Python vulkan test",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
CW_USEDEFAULT, CW_USEDEFAULT,
1280, 720,
NULL, NULL, mod, NULL
)
# Process events
asyncio.ensure_future(process_events(self.app))
def __del__(self):
# If the application did not exit using the conventional way
if self.__hwnd != None:
DestroyWindow(self.__hwnd)
UnregisterClassW(self.__class_name, GetModuleHandleW(None))
@property
def handle(self):
return self.__hwnd
def dimensions(self):
dim = RECT()
GetClientRect(self.__hwnd, byref(dim))
return (dim.right, dim.bottom)
def show(self):
ShowWindow(self.__hwnd, SW_SHOWNORMAL)
def set_title(self, title):
title = c_wchar_p(title)
SetWindowTextW(self.__hwnd, title)
class WinSwapchain(object):
def create_surface(self):
"""
Create a surface for the window
"""
app = self.app()
surface = vk.SurfaceKHR(0)
surface_info = vk.Win32SurfaceCreateInfoKHR(
s_type = vk.STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
next= None, flags=0, hinstance=GetModuleHandleW(None),
hwnd=app.window.handle
)
result = app.CreateWin32SurfaceKHR(app.instance, byref(surface_info), None, byref(surface))
if result == vk.SUCCESS:
self.surface = surface
else:
raise RuntimeError("Failed to create surface")
def __init__(self, app):
self.app = weakref.ref(app)
self.surface = None
self.swapchain = None
self.images = None
self.views = None
self.create_surface()