window.c 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. #define HL_NAME(n) directx_##n
  2. #include <hl.h>
  3. #define MAX_EVENTS 1024
  4. typedef enum {
  5. Quit = 0,
  6. MouseMove = 1,
  7. MouseLeave = 2,
  8. MouseDown = 3,
  9. MouseUp = 4,
  10. MouseWheel = 5,
  11. WindowState = 6,
  12. KeyDown = 7,
  13. KeyUp = 8,
  14. TextInput = 9,
  15. DropStart = 10,
  16. DropFile = 11,
  17. DropEnd = 12,
  18. } EventType;
  19. typedef enum {
  20. Show = 0,
  21. Hide = 1,
  22. Expose = 2,
  23. Move = 3,
  24. Resize = 4,
  25. Minimize= 5,
  26. Maximize= 6,
  27. Restore = 7,
  28. Enter = 8,
  29. Leave = 9,
  30. Focus = 10,
  31. Blur = 11,
  32. Close = 12
  33. } WindowStateChange;
  34. typedef enum {
  35. Hidden = 0x000001,
  36. Resizable = 0x000002
  37. } WindowFlags;
  38. typedef struct {
  39. hl_type *t;
  40. EventType type;
  41. int mouseX;
  42. int mouseY;
  43. int mouseXRel;
  44. int mouseYRel;
  45. int button;
  46. int wheelDelta;
  47. WindowStateChange state;
  48. int keyCode;
  49. int scanCode;
  50. bool keyRepeat;
  51. int controller;
  52. int value;
  53. vbyte* dropFile;
  54. } dx_event;
  55. typedef struct {
  56. dx_event events[MAX_EVENTS];
  57. DWORD normal_style;
  58. double opacity;
  59. int min_width;
  60. int min_height;
  61. int max_width;
  62. int max_height;
  63. int event_count;
  64. int next_event;
  65. bool is_over;
  66. bool is_focused;
  67. } dx_events;
  68. typedef struct HWND__ dx_window;
  69. static dx_window *cur_clip_cursor_window = NULL;
  70. static DWORD capture_refresh_time = 0;
  71. typedef enum {
  72. SkipUpdate = 1,
  73. InTitleClick = 2,
  74. LButton = 4,
  75. RButton = 8,
  76. MButton = 16,
  77. XButton1 = 32,
  78. XButton2 = 64
  79. } mouse_capture_flags;
  80. static int disable_capture = 0;
  81. static bool capture_mouse = false;
  82. static bool relative_mouse = false;
  83. typedef HCURSOR dx_cursor;
  84. static dx_cursor cur_cursor = NULL;
  85. static bool show_cursor = true;
  86. #define CURSOR_VISIBLE show_cursor && !relative_mouse
  87. static dx_events *get_events(HWND wnd) {
  88. return (dx_events*)GetWindowLongPtr(wnd,GWLP_USERDATA);
  89. }
  90. static void updateClipCursor(HWND wnd) {
  91. if ( disable_capture ) {
  92. ClipCursor(NULL);
  93. capture_refresh_time = GetTickCount();
  94. return;
  95. }
  96. if ( !capture_mouse && !relative_mouse ) {
  97. cur_clip_cursor_window = NULL;
  98. ClipCursor(NULL);
  99. } else {
  100. cur_clip_cursor_window = wnd;
  101. RECT rect;
  102. if ( GetClientRect(wnd, &rect) && !IsRectEmpty(&rect) ) {
  103. ClientToScreen(wnd, (LPPOINT)&rect.left);
  104. ClientToScreen(wnd, (LPPOINT)&rect.right);
  105. ClipCursor(&rect);
  106. }
  107. }
  108. capture_refresh_time = GetTickCount();
  109. }
  110. static void checkCaptureFlags( HWND wnd ) {
  111. if ( !(GetAsyncKeyState(VK_LBUTTON) & 0x8000) )
  112. disable_capture &= ~LButton;
  113. if ( !(GetAsyncKeyState(VK_RBUTTON) & 0x8000) )
  114. disable_capture &= ~RButton;
  115. if ( !(GetAsyncKeyState(VK_MBUTTON) & 0x8000) )
  116. disable_capture &= ~MButton;
  117. if ( !(GetAsyncKeyState(VK_XBUTTON1) & 0x8000) )
  118. disable_capture &= ~XButton1;
  119. if ( !(GetAsyncKeyState(VK_XBUTTON2) & 0x8000) )
  120. disable_capture &= ~XButton2;
  121. }
  122. static bool setRelativeMode( HWND wnd, bool enabled ) {
  123. RAWINPUTDEVICE mouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */
  124. if ( relative_mouse == enabled ) return true;
  125. if ( !enabled ) {
  126. mouse.dwFlags |= RIDEV_REMOVE;
  127. if ( show_cursor ) SetCursor(cur_cursor);
  128. } else {
  129. SetCursor(NULL);
  130. }
  131. relative_mouse = enabled;
  132. updateClipCursor(wnd);
  133. return RegisterRawInputDevices(&mouse, 1, sizeof(RAWINPUTDEVICE)) || !enabled;
  134. }
  135. static dx_event *addEvent( HWND wnd, EventType type ) {
  136. dx_events *buf = get_events(wnd);
  137. dx_event *e;
  138. if( buf->event_count == MAX_EVENTS )
  139. e = &buf->events[MAX_EVENTS-1];
  140. else
  141. e = &buf->events[buf->event_count++];
  142. e->type = type;
  143. return e;
  144. }
  145. #define addMouse(etype,but) { \
  146. e = addEvent(wnd,etype); \
  147. e->button = but; \
  148. e->mouseX = (short)LOWORD(lparam); \
  149. e->mouseY = (short)HIWORD(lparam); \
  150. }
  151. #define addState(wstate) { e = addEvent(wnd,WindowState); e->state = wstate; }
  152. static bool shift_downs[] = { false, false };
  153. static LRESULT CALLBACK WndProc( HWND wnd, UINT umsg, WPARAM wparam, LPARAM lparam ) {
  154. dx_event *e = NULL;
  155. switch(umsg) {
  156. case WM_DESTROY:
  157. PostQuitMessage(0);
  158. return 0;
  159. case WM_CREATE:
  160. SetWindowLongPtr(wnd,GWLP_USERDATA,(LONG_PTR)((CREATESTRUCT*)lparam)->lpCreateParams);
  161. break;
  162. case WM_SIZE:
  163. {
  164. dx_events *buf = get_events(wnd);
  165. if( buf->event_count > buf->next_event && buf->events[buf->event_count-1].type == WindowState && buf->events[buf->event_count-1].state == Resize )
  166. buf->event_count--;
  167. }
  168. addState(Resize);
  169. break;
  170. case WM_LBUTTONDOWN: addMouse(MouseDown,1); break;
  171. case WM_LBUTTONUP: addMouse(MouseUp,1); break;
  172. case WM_MBUTTONDOWN: addMouse(MouseDown,2); break;
  173. case WM_MBUTTONUP: addMouse(MouseUp,2); break;
  174. case WM_RBUTTONDOWN: addMouse(MouseDown,3); break;
  175. case WM_RBUTTONUP: addMouse(MouseUp,3); break;
  176. case WM_XBUTTONDOWN: addMouse(MouseDown,3 + HIWORD(wparam)); break;
  177. case WM_XBUTTONUP: addMouse(MouseUp,3 + HIWORD(wparam)); break;
  178. case WM_MOUSEWHEEL: addMouse(MouseWheel,0); e->wheelDelta = ((int)(short)HIWORD(wparam)) / 120; break;
  179. case WM_SYSCOMMAND:
  180. if( wparam == SC_KEYMENU && (lparam>>16) <= 0 )
  181. return 0;
  182. break;
  183. case WM_MOUSEMOVE:
  184. {
  185. dx_events *evt = get_events(wnd);
  186. if( !evt->is_over ) {
  187. TRACKMOUSEEVENT ev;
  188. ev.cbSize = sizeof(ev);
  189. ev.dwFlags = TME_LEAVE;
  190. ev.hwndTrack = wnd;
  191. TrackMouseEvent(&ev);
  192. evt->is_over = true;
  193. addState(Enter);
  194. }
  195. if ( !relative_mouse )
  196. addMouse(MouseMove, 0);
  197. }
  198. break;
  199. case WM_INPUT:
  200. {
  201. dx_events* evt = get_events(wnd);
  202. if ( evt->is_focused ) {
  203. // Only handle raw input when window is active
  204. HRAWINPUT hRawInput = (HRAWINPUT)lparam;
  205. RAWINPUT inp;
  206. UINT size = sizeof(inp);
  207. GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER));
  208. // Ignore pseudo-movement from touch events.
  209. if ( inp.header.dwType == RIM_TYPEMOUSE ) {
  210. RAWMOUSE* mouse = &inp.data.mouse;
  211. if ( (mouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE ) {
  212. if ( mouse->lLastX != 0 || mouse->lLastY != 0 ) {
  213. e = addEvent(wnd, MouseMove);
  214. e->mouseX = 0;
  215. e->mouseY = 0;
  216. e->button = 0;
  217. e->mouseXRel = mouse->lLastX;
  218. e->mouseYRel = mouse->lLastY;
  219. }
  220. } else if ( mouse->lLastX || mouse->lLastY ) {
  221. // Absolute movement - simulate relative movement
  222. static POINT lastMousePos;
  223. bool virtual_desktop = mouse->usFlags & MOUSE_VIRTUAL_DESKTOP;
  224. int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
  225. int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
  226. int x = (int)(((float)mouse->lLastX / 65535.0f) * w);
  227. int y = (int)(((float)mouse->lLastY / 65535.0f) * h);
  228. if (lastMousePos.x == 0 && lastMousePos.y == 0) {
  229. lastMousePos.x = x;
  230. lastMousePos.y = y;
  231. } else {
  232. e = addEvent(wnd, MouseMove);
  233. e->mouseX = 0;
  234. e->mouseY = 0;
  235. e->button = 0;
  236. e->mouseXRel = x - lastMousePos.x;
  237. e->mouseYRel = y - lastMousePos.y;
  238. lastMousePos.x = x;
  239. lastMousePos.y = y;
  240. }
  241. }
  242. }
  243. }
  244. }
  245. break;
  246. case WM_MOUSELEAVE:
  247. addState(Leave);
  248. get_events(wnd)->is_over = false;
  249. break;
  250. case WM_KEYDOWN:
  251. case WM_SYSKEYDOWN:
  252. case WM_KEYUP:
  253. case WM_SYSKEYUP:
  254. // right alt has triggered a control !
  255. if( wparam == VK_MENU && lparam & (1<<24) ) {
  256. dx_events *buf = get_events(wnd);
  257. if( buf->event_count > buf->next_event && buf->events[buf->event_count-1].type == (umsg == WM_KEYUP ? KeyUp : KeyDown) && buf->events[buf->event_count-1].keyCode == (VK_CONTROL|256) ) {
  258. buf->event_count--;
  259. //printf("CANCEL\n");
  260. }
  261. }
  262. bool repeat = (umsg == WM_KEYDOWN || umsg == WM_SYSKEYDOWN) && (lparam & 0x40000000) != 0;
  263. // see
  264. e = addEvent(wnd,(umsg == WM_KEYUP || umsg == WM_SYSKEYUP) ? KeyUp : KeyDown);
  265. e->keyCode = (int)wparam;
  266. e->scanCode = (lparam >> 16) & 0xFF;
  267. e->keyRepeat = repeat;
  268. // L/R location
  269. if( e->keyCode == VK_SHIFT ) {
  270. bool right = MapVirtualKey((lparam >> 16) & 0xFF, MAPVK_VSC_TO_VK_EX) == VK_RSHIFT;
  271. e->keyCode |= right ? 512 : 256;
  272. e->keyRepeat = false;
  273. shift_downs[right?1:0] = e->type == KeyDown;
  274. }
  275. if( e->keyCode == VK_SHIFT || e->keyCode == VK_CONTROL || e->keyCode == VK_MENU )
  276. e->keyCode |= (lparam & (1<<24)) ? 512 : 256;
  277. if( e->keyCode == 13 && (lparam & 0x1000000) )
  278. e->keyCode = 108; // numpad enter
  279. //printf("%.8X - %d[%s]%s%s\n",lparam,e->keyCode&255,e->type == KeyUp ? "UP":"DOWN",e->keyRepeat?" REPEAT":"",(e->keyCode&256) ? " LEFT" : (e->keyCode & 512) ? " RIGHT" : "");
  280. if( (e->keyCode & 0xFF) == VK_MENU )
  281. return 0;
  282. break;
  283. case WM_TIMER:
  284. // bugfix for shifts being considered as a single key (one single WM_KEYUP is received when both are down)
  285. if( shift_downs[0] && GetKeyState(VK_LSHIFT) >= 0 ) {
  286. //printf("LSHIFT RELEASED\n");
  287. shift_downs[0] = false;
  288. e = addEvent(wnd,KeyUp);
  289. e->keyCode = VK_SHIFT | 256;
  290. }
  291. if( shift_downs[1] && GetKeyState(VK_RSHIFT) >= 0 ) {
  292. //printf("RSHIFT RELEASED\n");
  293. shift_downs[1] = false;
  294. e = addEvent(wnd,KeyUp);
  295. e->keyCode = VK_SHIFT | 512;
  296. }
  297. if ( ( capture_mouse || relative_mouse ) && get_events(wnd)->is_focused ) {
  298. // Refresh the cursor capture every 3s in case some app hijacks it.
  299. if ( disable_capture ) {
  300. checkCaptureFlags(wnd);
  301. if (!disable_capture || disable_capture == SkipUpdate) {
  302. disable_capture = 0;
  303. updateClipCursor(wnd);
  304. }
  305. } else if ( GetTickCount() - capture_refresh_time >= 3000 ) {
  306. disable_capture &= ~SkipUpdate;
  307. updateClipCursor(wnd);
  308. }
  309. }
  310. break;
  311. case WM_CHAR:
  312. e = addEvent(wnd,TextInput);
  313. e->keyCode = (int)wparam;
  314. e->keyRepeat = (lparam & 0xFFFF) != 0;
  315. break;
  316. case WM_NCACTIVATE:
  317. // Allow user to interact with the titlebar without clipping.
  318. disable_capture |= SkipUpdate;
  319. ClipCursor(NULL);
  320. break;
  321. case WM_ACTIVATE:
  322. // HIWORD(wparam) = minimized flag
  323. if (!(bool)HIWORD(wparam) && LOWORD(wparam) != WA_INACTIVE) {
  324. if ( LOWORD(wparam) == WA_CLICKACTIVE ) {
  325. if ( GetAsyncKeyState(VK_LBUTTON) )
  326. disable_capture |= LButton;
  327. if ( GetAsyncKeyState(VK_RBUTTON) )
  328. disable_capture |= RButton;
  329. if ( GetAsyncKeyState(VK_MBUTTON) )
  330. disable_capture |= MButton;
  331. if ( GetAsyncKeyState(VK_XBUTTON1) )
  332. disable_capture |= XButton1;
  333. if ( GetAsyncKeyState(VK_XBUTTON2) )
  334. disable_capture |= XButton2;
  335. }
  336. checkCaptureFlags(wnd);
  337. updateClipCursor(wnd);
  338. } else {
  339. ClipCursor(NULL);
  340. }
  341. break;
  342. case WM_NCLBUTTONDOWN:
  343. disable_capture |= InTitleClick;
  344. ClipCursor(NULL);
  345. break;
  346. case WM_CAPTURECHANGED:
  347. disable_capture &= ~InTitleClick;
  348. checkCaptureFlags(wnd);
  349. updateClipCursor(wnd);
  350. break;
  351. case WM_SETFOCUS:
  352. get_events(wnd)->is_focused = true;
  353. updateClipCursor(wnd);
  354. addState(Focus);
  355. break;
  356. case WM_KILLFOCUS:
  357. shift_downs[0] = false;
  358. shift_downs[1] = false;
  359. if ( capture_mouse || relative_mouse ) ClipCursor(NULL);
  360. get_events(wnd)->is_focused = false;
  361. addState(Blur);
  362. break;
  363. case WM_WINDOWPOSCHANGED:
  364. updateClipCursor(wnd);
  365. break;
  366. case WM_GETMINMAXINFO:
  367. {
  368. dx_events *buf = get_events(wnd);
  369. if( buf ) {
  370. long resizable_flags = (WS_MAXIMIZEBOX | WS_THICKFRAME);
  371. if( (buf->normal_style & resizable_flags) == resizable_flags ) {
  372. if( buf->min_width != 0 || buf->min_height != 0 ||
  373. buf->max_width != 0 || buf->max_height != 0 ) {
  374. MINMAXINFO *info = (MINMAXINFO*)lparam;
  375. RECT r;
  376. GetClientRect(wnd, &r);
  377. int curr_width = r.right;
  378. int curr_height = r.bottom;
  379. // remove curr_width/height which contain non-client dimensions
  380. bool apply_max = false;
  381. int min_width = buf->min_width - curr_width;
  382. int min_height = buf->min_height - curr_height;
  383. int max_width = buf->max_width;
  384. int max_height = buf->max_height;
  385. if( max_width && max_height ) {
  386. max_width -= curr_width;
  387. max_height -= curr_height;
  388. apply_max = true;
  389. }
  390. // fix curr_width/height to contain only client size
  391. {
  392. RECT size;
  393. LONG style = GetWindowLong(wnd, GWL_STYLE);
  394. BOOL menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(wnd) != NULL);
  395. size.top = 0;
  396. size.left = 0;
  397. size.bottom = curr_height;
  398. size.right = curr_width;
  399. AdjustWindowRectEx(&size, style, menu, 0);
  400. curr_width = size.right - size.left;
  401. curr_height = size.bottom - size.top;
  402. }
  403. // apply curr_width/height without client size
  404. info->ptMinTrackSize.x = min_width + curr_width;
  405. info->ptMinTrackSize.y = min_height + curr_height;
  406. if( apply_max ) {
  407. info->ptMaxTrackSize.x = max_width + curr_width;
  408. info->ptMaxTrackSize.y = max_height + curr_height;
  409. }
  410. return 0;
  411. }
  412. }
  413. }
  414. break;
  415. }
  416. case WM_SETCURSOR:
  417. if( LOWORD(lparam) == HTCLIENT ) {
  418. if( CURSOR_VISIBLE )
  419. SetCursor(cur_cursor != NULL ? cur_cursor : LoadCursor(NULL, IDC_ARROW));
  420. else
  421. SetCursor(NULL);
  422. return TRUE;
  423. }
  424. break;
  425. case WM_DROPFILES:
  426. {
  427. HDROP drop = (HDROP)wparam;
  428. UINT count = DragQueryFileW(drop, 0xFFFFFFFF, NULL, 0);
  429. POINT dragPoint;
  430. if ( !DragQueryPoint(drop, &dragPoint) ) {
  431. dragPoint.x = 0L;
  432. dragPoint.y = 0L;
  433. }
  434. e = addEvent(wnd, DropStart);
  435. e->value = count;
  436. e->mouseX = (int)dragPoint.x;
  437. e->mouseY = (int)dragPoint.y;
  438. for ( UINT i = 0; i < count; i++ ) {
  439. UINT size = DragQueryFileW(drop, i, NULL, 0) + 1; // + zero terminator
  440. // We have to make a temporary unmanaged buffer copy due to async nature of event being
  441. // processed and collected by Haxe event loop, and using GC-allocated buffer risks
  442. // resulting in garbage data if GC is ran at any point inbetween due to freed buffer.
  443. // As a consequence, this event requires checks during fetching of the next event
  444. // (and window destruction) in order to ensure Haxe side gets proper buffer without memory leaks.
  445. vbyte* buffer = malloc(size * sizeof(WCHAR));
  446. if ( DragQueryFileW(drop, i, (LPWSTR)buffer, size) ) {
  447. e = addEvent(wnd, DropFile);
  448. e->value = size * sizeof(WCHAR);
  449. e->dropFile = buffer;
  450. e->mouseX = (int)dragPoint.x;
  451. e->mouseY = (int)dragPoint.y;
  452. } else {
  453. free(buffer);
  454. }
  455. }
  456. e = addEvent(wnd, DropEnd);
  457. e->value = count;
  458. e->mouseX = (int)dragPoint.x;
  459. e->mouseY = (int)dragPoint.y;
  460. DragFinish(drop);
  461. break;
  462. }
  463. case WM_CLOSE:
  464. addEvent(wnd, Quit);
  465. return 0;
  466. }
  467. return DefWindowProc(wnd, umsg, wparam, lparam);
  468. }
  469. HL_PRIM dx_window *HL_NAME(win_create_ex)( int x, int y, int width, int height, WindowFlags windowFlags ) {
  470. static bool wnd_class_reg = false;
  471. HINSTANCE hinst = GetModuleHandle(NULL);
  472. if( !wnd_class_reg ) {
  473. WNDCLASSEX wc;
  474. wchar_t fileName[1024];
  475. GetModuleFileName(hinst,fileName,1024);
  476. wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  477. wc.lpfnWndProc = WndProc;
  478. wc.cbClsExtra = 0;
  479. wc.cbWndExtra = 0;
  480. wc.hInstance = hinst;
  481. wc.hIcon = ExtractIcon(hinst, fileName, 0);
  482. wc.hIconSm = wc.hIcon;
  483. wc.hCursor = NULL;
  484. wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
  485. wc.lpszMenuName = NULL;
  486. wc.lpszClassName = USTR("HL_WIN");
  487. wc.cbSize = sizeof(WNDCLASSEX);
  488. RegisterClassEx(&wc);
  489. }
  490. RECT r;
  491. DWORD style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
  492. if( !(windowFlags & Resizable) ) {
  493. style &= ~( WS_MAXIMIZEBOX | WS_THICKFRAME );
  494. }
  495. r.left = r.top = 0;
  496. r.right = width;
  497. r.bottom = height;
  498. AdjustWindowRect(&r,style,false);
  499. dx_events *event_buffer = (dx_events*)malloc(sizeof(dx_events));
  500. memset(event_buffer,0, sizeof(dx_events));
  501. event_buffer->normal_style = style;
  502. event_buffer->opacity = 1.0;
  503. dx_window *win = CreateWindowEx(WS_EX_APPWINDOW, USTR("HL_WIN"), USTR(""), style, x, y, r.right - r.left, r.bottom - r.top, NULL, NULL, hinst, event_buffer);
  504. SetTimer(win,0,10,NULL);
  505. if( !(windowFlags & Hidden) ) {
  506. ShowWindow(win, SW_SHOW);
  507. }
  508. SetForegroundWindow(win);
  509. SetFocus(win);
  510. return win;
  511. }
  512. HL_PRIM dx_window *HL_NAME(win_create)( int width, int height ) {
  513. return HL_NAME(win_create_ex)(CW_USEDEFAULT, CW_USEDEFAULT, width, height, Resizable);
  514. }
  515. HL_PRIM void HL_NAME(win_set_title)(dx_window *win, vbyte *title) {
  516. SetWindowText(win,(LPCWSTR)title);
  517. }
  518. HL_PRIM void HL_NAME(win_set_size)(dx_window *win, int width, int height) {
  519. RECT r;
  520. GetWindowRect(win,&r);
  521. r.left = r.top = 0;
  522. r.right = width;
  523. r.bottom = height;
  524. AdjustWindowRectEx(&r,GetWindowLong(win,GWL_STYLE),GetMenu(win) != NULL,GetWindowLong(win,GWL_EXSTYLE));
  525. SetWindowPos(win,NULL,0,0,r.right - r.left,r.bottom - r.top,SWP_NOMOVE|SWP_NOOWNERZORDER);
  526. }
  527. HL_PRIM void HL_NAME(win_get_size)(dx_window *win, int *width, int *height) {
  528. RECT r;
  529. GetClientRect(win,&r);
  530. if( width ) *width = r.right;
  531. if( height ) *height = r.bottom;
  532. }
  533. HL_PRIM void HL_NAME(win_set_min_size)(dx_window *win, int width, int height) {
  534. dx_events *buf = get_events(win);
  535. if( width < 0 ) width = 0;
  536. if( height < 0 ) height = 0;
  537. if( buf->max_width != 0 && width > buf->max_width ) width = buf->max_width;
  538. if( buf->max_height != 0 && height > buf->max_height ) height = buf->max_height;
  539. if( buf->min_width != width || buf->min_height != height ) {
  540. buf->min_width = width;
  541. buf->min_height = height;
  542. int curr_width = 0;
  543. int curr_height = 0;
  544. HL_NAME(win_get_size)(win, &curr_width, &curr_height);
  545. if( curr_width < width || curr_height < height )
  546. HL_NAME(win_set_size)(win, max(curr_width, width), max(curr_height, height));
  547. }
  548. }
  549. HL_PRIM void HL_NAME(win_get_min_size)(dx_window *win, int *width, int *height) {
  550. dx_events *buf = get_events(win);
  551. if( width ) *width = buf->min_width;
  552. if( height ) *height = buf->min_height;
  553. }
  554. HL_PRIM void HL_NAME(win_set_max_size)(dx_window *win, int width, int height) {
  555. dx_events *buf = get_events(win);
  556. if( width == 0 && height == 0 ) {
  557. buf->max_width = 0;
  558. buf->max_height = 0;
  559. } else {
  560. if( width < buf->min_width ) width = buf->min_width;
  561. if( height < buf->min_height ) height = buf->min_height;
  562. if( width == 0 ) width = 1;
  563. if( height == 0 ) height = 1;
  564. if( buf->max_width != width || buf->max_height != height ) {
  565. buf->max_width = width;
  566. buf->max_height = height;
  567. int curr_width = 0;
  568. int curr_height = 0;
  569. HL_NAME(win_get_size)(win, &curr_width, &curr_height);
  570. if( curr_width > width || curr_height > height )
  571. HL_NAME(win_set_size)(win, min(curr_width, width), min(curr_height, height));
  572. }
  573. }
  574. }
  575. HL_PRIM void HL_NAME(win_get_max_size)(dx_window *win, int *width, int *height) {
  576. dx_events *buf = get_events(win);
  577. if( width ) *width = buf->max_width;
  578. if( height ) *height = buf->max_height;
  579. }
  580. HL_PRIM void HL_NAME(win_get_position)(dx_window *win, int *x, int *y) {
  581. RECT r;
  582. GetWindowRect(win,&r);
  583. if( x ) *x = r.left;
  584. if( y ) *y = r.top;
  585. }
  586. HL_PRIM void HL_NAME(win_set_position)(dx_window *win, int x, int y) {
  587. SetWindowPos(win,NULL,x,y,0,0,SWP_NOSIZE|SWP_NOZORDER);
  588. }
  589. // initially written with the intent to center on closest monitor; however, SDL centers to primary, so both options are provided
  590. HL_PRIM void HL_NAME(win_center)(dx_window *win, bool centerPrimary) {
  591. int scnX = 0;
  592. int scnY = 0;
  593. int scnWidth = -1;
  594. int scnHeight = -1;
  595. if( centerPrimary ) {
  596. scnWidth = GetSystemMetrics(SM_CXSCREEN);
  597. scnHeight = GetSystemMetrics(SM_CYSCREEN);
  598. } else {
  599. HMONITOR m = MonitorFromWindow(win, MONITOR_DEFAULTTONEAREST);
  600. if( m != NULL ) {
  601. MONITORINFO info;
  602. info.cbSize = sizeof(MONITORINFO);
  603. GetMonitorInfo(m, &info);
  604. RECT screen = info.rcMonitor; // "rcMonitor" to match SM_CXSCREEN/SM_CYSCREEN measurements
  605. scnX = screen.left;
  606. scnY = screen.top;
  607. scnWidth = (screen.right - scnX);
  608. scnHeight = (screen.bottom - scnY);
  609. }
  610. }
  611. if( scnWidth >= 0 && scnHeight >= 0 ) {
  612. int winWidth = 0;
  613. int winHeight = 0;
  614. HL_NAME(win_get_size)(win, &winWidth, &winHeight);
  615. HL_NAME(win_set_position)(win, scnX + ((scnWidth - winWidth) / 2), scnY + ((scnHeight - winHeight) / 2));
  616. }
  617. }
  618. HL_PRIM void HL_NAME(win_resize)(dx_window *win, int mode) {
  619. switch( mode ) {
  620. case 0:
  621. ShowWindow(win, SW_MAXIMIZE);
  622. break;
  623. case 1:
  624. ShowWindow(win, SW_MINIMIZE);
  625. break;
  626. case 2:
  627. ShowWindow(win, SW_RESTORE);
  628. break;
  629. case 3:
  630. ShowWindow(win, SW_HIDE);
  631. break;
  632. case 4:
  633. ShowWindow(win, SW_SHOW);
  634. break;
  635. default:
  636. break;
  637. }
  638. }
  639. HL_PRIM void HL_NAME(win_set_fullscreen)(dx_window *win, bool fs) {
  640. if( fs ) {
  641. MONITORINFO mi = { sizeof(mi) };
  642. GetMonitorInfo(MonitorFromWindow(win,MONITOR_DEFAULTTOPRIMARY), &mi);
  643. SetWindowLong(win,GWL_STYLE,WS_POPUP | WS_VISIBLE);
  644. SetWindowPos(win,NULL,mi.rcMonitor.left,mi.rcMonitor.top,mi.rcMonitor.right - mi.rcMonitor.left,mi.rcMonitor.bottom - mi.rcMonitor.top,SWP_NOOWNERZORDER|SWP_FRAMECHANGED|SWP_SHOWWINDOW);
  645. } else {
  646. dx_events *buf = get_events(win);
  647. SetWindowLong(win,GWL_STYLE,buf->normal_style);
  648. SetWindowPos(win,NULL,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOOWNERZORDER|SWP_FRAMECHANGED|SWP_SHOWWINDOW);
  649. }
  650. }
  651. HL_PRIM double HL_NAME(win_get_opacity)(dx_window *win) {
  652. dx_events *buf = get_events(win);
  653. return buf->opacity;
  654. }
  655. HL_PRIM bool HL_NAME(win_set_opacity)(dx_window *win, double opacity) {
  656. if( opacity > 1.0 )
  657. opacity = 1.0;
  658. else if( opacity < 0.0 )
  659. opacity = 0.0;
  660. dx_events *buf = get_events(win);
  661. if( buf->opacity != opacity ) {
  662. buf->opacity = opacity;
  663. LONG style = GetWindowLong(win, GWL_EXSTYLE);
  664. bool layered = (style & WS_EX_LAYERED) != 0;
  665. if( opacity >= 1.0 ) {
  666. if( layered )
  667. if( SetWindowLong(win, GWL_EXSTYLE, style & ~WS_EX_LAYERED) == 0 )
  668. return false;
  669. } else {
  670. if( !layered )
  671. if( SetWindowLong(win, GWL_EXSTYLE, style | WS_EX_LAYERED) == 0 )
  672. return false;
  673. if( SetLayeredWindowAttributes(win, 0, (BYTE)((int)(opacity * 255.0)), LWA_ALPHA) == 0 )
  674. return false;
  675. }
  676. }
  677. return true;
  678. }
  679. HL_PRIM void HL_NAME(win_destroy)(dx_window *win) {
  680. if (cur_clip_cursor_window == win) {
  681. cur_clip_cursor_window = NULL;
  682. ClipCursor(NULL);
  683. }
  684. dx_events *buf = get_events(win);
  685. // See WM_DROPFILES comment regarding GC
  686. for ( int i = buf->next_event; i < buf->event_count; i++ ) {
  687. if ( buf->events[i].dropFile != NULL )
  688. free(buf->events[i].dropFile);
  689. }
  690. free(buf);
  691. SetWindowLongPtr(win,GWLP_USERDATA,0);
  692. DestroyWindow(win);
  693. }
  694. HL_PRIM bool HL_NAME(win_get_next_event)( dx_window *win, dx_event *e ) {
  695. dx_events *buf = get_events(win);
  696. hl_type *save;
  697. if( !buf ) {
  698. e->type = Quit;
  699. return true;
  700. }
  701. if( buf->next_event == buf->event_count ) {
  702. buf->next_event = buf->event_count = 0;
  703. return false;
  704. }
  705. save = e->t;
  706. memcpy(e,&buf->events[buf->next_event++],sizeof(dx_event));
  707. if ( e->type == DropFile ) {
  708. // See WM_DROPFILES comment regarding GC
  709. vbyte* unmanaged = e->dropFile;
  710. e->dropFile = hl_copy_bytes(unmanaged, e->value);
  711. free(unmanaged);
  712. buf->events[buf->next_event - 1].dropFile = NULL;
  713. }
  714. e->t = save;
  715. return true;
  716. }
  717. HL_PRIM void HL_NAME(win_clip_cursor)(dx_window *win, bool enable) {
  718. capture_mouse = enable;
  719. updateClipCursor(win);
  720. }
  721. HL_PRIM bool HL_NAME(set_cursor_pos)( int x, int y ) {
  722. return SetCursorPos(x, y);
  723. }
  724. HL_PRIM bool HL_NAME(win_set_cursor_pos)( dx_window *wnd, int x, int y) {
  725. if ( wnd ) {
  726. POINT pt;
  727. pt.x = x;
  728. pt.y = y;
  729. ClientToScreen(wnd, &pt);
  730. return SetCursorPos(pt.x, pt.y);
  731. }
  732. return false;
  733. }
  734. HL_PRIM bool HL_NAME(win_set_relative_mouse_mode)( dx_window *wnd, bool enabled) {
  735. return setRelativeMode(wnd, enabled);
  736. }
  737. HL_PRIM bool HL_NAME(win_get_relative_mouse_mode)() {
  738. return relative_mouse;
  739. }
  740. HL_PRIM void HL_NAME(win_set_drag_accept_files)( dx_window* wnd, bool enabled ) {
  741. DragAcceptFiles(wnd, enabled);
  742. }
  743. HL_PRIM int HL_NAME(get_screen_width)() {
  744. return GetSystemMetrics(SM_CXSCREEN);
  745. }
  746. HL_PRIM int HL_NAME(get_screen_height)() {
  747. return GetSystemMetrics(SM_CYSCREEN);
  748. }
  749. typedef struct {
  750. int idx;
  751. varray* arr;
  752. } get_monitors_data;
  753. BOOL CALLBACK on_get_monitors(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM param) {
  754. get_monitors_data *data = (get_monitors_data*)param;
  755. varray* arr = data->arr;
  756. MONITORINFOEXW info;
  757. info.cbSize = sizeof(MONITORINFOEXW);
  758. GetMonitorInfoW(monitor, (LPMONITORINFO)&info);
  759. vdynamic* dynobj = (vdynamic*)hl_alloc_dynobj();
  760. hl_dyn_seti(dynobj, hl_hash_utf8("left"), &hlt_i32, rect->left);
  761. hl_dyn_seti(dynobj, hl_hash_utf8("right"), &hlt_i32, rect->right);
  762. hl_dyn_seti(dynobj, hl_hash_utf8("top"), &hlt_i32, rect->top);
  763. hl_dyn_seti(dynobj, hl_hash_utf8("bottom"), &hlt_i32, rect->bottom);
  764. hl_dyn_setp(dynobj, hl_hash_utf8("name"), &hlt_bytes, hl_copy_bytes((vbyte*)info.szDevice, (int)(wcslen(info.szDevice)+1)*2));
  765. hl_aptr(arr, vdynobj*)[data->idx++] = (vdynobj*)dynobj;
  766. return TRUE;
  767. }
  768. HL_PRIM varray* HL_NAME(win_get_monitors)() {
  769. get_monitors_data data;
  770. data.idx = 0;
  771. data.arr = hl_alloc_array(&hlt_dynobj, 64);
  772. EnumDisplayMonitors(NULL, NULL, on_get_monitors, (LPARAM)&data);
  773. data.arr->size = data.idx;
  774. return data.arr;
  775. }
  776. HL_PRIM vbyte* HL_NAME(win_get_monitor_from_window)(HWND wnd) {
  777. HMONITOR handle = MonitorFromWindow(wnd, MONITOR_DEFAULTTOPRIMARY);
  778. MONITORINFOEX info;
  779. info.cbSize = sizeof(MONITORINFOEX);
  780. if (!GetMonitorInfo(handle, (LPMONITORINFO)&info))
  781. return NULL;
  782. return hl_copy_bytes((vbyte*)info.szDevice, (int)(wcslen(info.szDevice) + 1) * 2);
  783. }
  784. HL_PRIM varray* HL_NAME(win_get_display_settings)(wchar_t* device) {
  785. DEVMODEW ds;
  786. ds.dmSize = sizeof(DEVMODEW);
  787. int len = 0;
  788. while (EnumDisplaySettingsW(device, len, &ds))
  789. len++;
  790. varray* arr = hl_alloc_array(&hlt_dynobj, len);
  791. for (int i = 0; EnumDisplaySettingsW(device, i, &ds); i++) {
  792. vdynamic* dynobj = (vdynamic*) hl_alloc_dynobj();
  793. hl_dyn_seti(dynobj, hl_hash_utf8("width"), &hlt_i32, ds.dmPelsWidth);
  794. hl_dyn_seti(dynobj, hl_hash_utf8("height"), &hlt_i32, ds.dmPelsHeight);
  795. hl_dyn_seti(dynobj, hl_hash_utf8("framerate"), &hlt_i32, ds.dmDisplayFrequency);
  796. hl_aptr(arr, vdynobj*)[i] = (vdynobj*) dynobj;
  797. }
  798. return arr;
  799. }
  800. HL_PRIM vdynamic* HL_NAME(win_get_current_display_setting)(wchar_t* device, bool registry) {
  801. DEVMODEW ds;
  802. ds.dmSize = sizeof(DEVMODEW);
  803. EnumDisplaySettingsW(device, registry ? ENUM_REGISTRY_SETTINGS : ENUM_CURRENT_SETTINGS, &ds);
  804. vdynamic* dynobj = (vdynamic*) hl_alloc_dynobj();
  805. hl_dyn_seti(dynobj, hl_hash_utf8("width"), &hlt_i32, ds.dmPelsWidth);
  806. hl_dyn_seti(dynobj, hl_hash_utf8("height"), &hlt_i32, ds.dmPelsHeight);
  807. hl_dyn_seti(dynobj, hl_hash_utf8("framerate"), &hlt_i32, ds.dmDisplayFrequency);
  808. return dynobj;
  809. }
  810. HL_PRIM int HL_NAME(win_change_display_setting)(wchar_t* device, vdynamic* ds) {
  811. bool found = false;
  812. DEVMODEW devMode;
  813. devMode.dmSize = sizeof(DEVMODEW);
  814. if (ds != NULL) {
  815. int width = hl_dyn_geti(ds, hl_hash_utf8("width"), &hlt_i32);
  816. int height = hl_dyn_geti(ds, hl_hash_utf8("height"), &hlt_i32);
  817. int framerate = hl_dyn_geti(ds, hl_hash_utf8("framerate"), &hlt_i32);
  818. for (int i = 0; EnumDisplaySettingsW(device, i, &devMode); i++) {
  819. if (devMode.dmPelsWidth == width && devMode.dmPelsHeight == height && devMode.dmDisplayFrequency == framerate) {
  820. found = true;
  821. break;
  822. }
  823. }
  824. }
  825. return ChangeDisplaySettingsExW(device, found ? &devMode : NULL, NULL, found ? CDS_FULLSCREEN : 0, NULL);
  826. }
  827. #define TWIN _ABSTRACT(dx_window)
  828. DEFINE_PRIM(TWIN, win_create_ex, _I32 _I32 _I32 _I32 _I32);
  829. DEFINE_PRIM(TWIN, win_create, _I32 _I32);
  830. DEFINE_PRIM(_VOID, win_set_fullscreen, TWIN _BOOL);
  831. DEFINE_PRIM(_VOID, win_resize, TWIN _I32);
  832. DEFINE_PRIM(_VOID, win_set_title, TWIN _BYTES);
  833. DEFINE_PRIM(_VOID, win_set_size, TWIN _I32 _I32);
  834. DEFINE_PRIM(_VOID, win_set_min_size, TWIN _I32 _I32);
  835. DEFINE_PRIM(_VOID, win_set_max_size, TWIN _I32 _I32);
  836. DEFINE_PRIM(_VOID, win_set_position, TWIN _I32 _I32);
  837. DEFINE_PRIM(_VOID, win_center, TWIN _BOOL);
  838. DEFINE_PRIM(_VOID, win_get_size, TWIN _REF(_I32) _REF(_I32));
  839. DEFINE_PRIM(_VOID, win_get_min_size, TWIN _REF(_I32) _REF(_I32));
  840. DEFINE_PRIM(_VOID, win_get_max_size, TWIN _REF(_I32) _REF(_I32));
  841. DEFINE_PRIM(_VOID, win_get_position, TWIN _REF(_I32) _REF(_I32));
  842. DEFINE_PRIM(_F64, win_get_opacity, TWIN);
  843. DEFINE_PRIM(_BOOL, win_set_opacity, TWIN _F64);
  844. DEFINE_PRIM(_VOID, win_destroy, TWIN);
  845. DEFINE_PRIM(_BOOL, win_get_next_event, TWIN _DYN);
  846. DEFINE_PRIM(_VOID, win_clip_cursor, TWIN _BOOL);
  847. DEFINE_PRIM(_BOOL, set_cursor_pos, _I32 _I32);
  848. DEFINE_PRIM(_BOOL, win_set_cursor_pos, TWIN _I32 _I32);
  849. DEFINE_PRIM(_BOOL, win_set_relative_mouse_mode, TWIN _BOOL);
  850. DEFINE_PRIM(_BOOL, win_get_relative_mouse_mode, _NO_ARG);
  851. DEFINE_PRIM(_VOID, win_set_drag_accept_files, TWIN _BOOL);
  852. DEFINE_PRIM(_ARR, win_get_display_settings, _BYTES);
  853. DEFINE_PRIM(_DYN, win_get_current_display_setting, _BYTES _BOOL);
  854. DEFINE_PRIM(_I32, win_change_display_setting, _BYTES _DYN);
  855. DEFINE_PRIM(_ARR, win_get_monitors, _NO_ARG);
  856. DEFINE_PRIM(_BYTES, win_get_monitor_from_window, TWIN);
  857. DEFINE_PRIM(_I32, get_screen_width, _NO_ARG);
  858. DEFINE_PRIM(_I32, get_screen_height, _NO_ARG);
  859. HL_PRIM dx_cursor HL_NAME(load_cursor)( int res ) {
  860. return LoadCursor(NULL,MAKEINTRESOURCE(res));
  861. }
  862. HL_PRIM dx_cursor HL_NAME(create_cursor)( int width, int height, vbyte *data, int hotX, int hotY ) {
  863. int pad = sizeof(void*) << 3;
  864. HICON hicon;
  865. HDC hdc = GetDC(NULL);
  866. BITMAPV4HEADER bmh;
  867. void *pixels;
  868. void *maskbits;
  869. int maskbitslen;
  870. ICONINFO ii;
  871. ZeroMemory(&bmh,sizeof(bmh));
  872. bmh.bV4Size = sizeof(bmh);
  873. bmh.bV4Width = width;
  874. bmh.bV4Height = -height;
  875. bmh.bV4Planes = 1;
  876. bmh.bV4BitCount = 32;
  877. bmh.bV4V4Compression = BI_BITFIELDS;
  878. bmh.bV4AlphaMask = 0xFF000000;
  879. bmh.bV4RedMask = 0x00FF0000;
  880. bmh.bV4GreenMask = 0x0000FF00;
  881. bmh.bV4BlueMask = 0x000000FF;
  882. maskbitslen = ((width + (-width)%pad) >> 3) * height;
  883. maskbits = malloc(maskbitslen);
  884. if( maskbits == NULL )
  885. return NULL;
  886. memset(maskbits,0xFF,maskbitslen);
  887. memset(&ii,0,sizeof(ii));
  888. ii.fIcon = FALSE;
  889. ii.xHotspot = (DWORD)hotX;
  890. ii.yHotspot = (DWORD)hotY;
  891. ii.hbmColor = CreateDIBSection(hdc, (BITMAPINFO*)&bmh, DIB_RGB_COLORS, &pixels, NULL, 0);
  892. ii.hbmMask = CreateBitmap(width, height, 1, 1, maskbits);
  893. ReleaseDC(NULL, hdc);
  894. free(maskbits);
  895. memcpy(pixels, data, height * width * 4);
  896. hicon = CreateIconIndirect(&ii);
  897. DeleteObject(ii.hbmColor);
  898. DeleteObject(ii.hbmMask);
  899. return hicon;
  900. }
  901. HL_PRIM void HL_NAME(destroy_cursor)( dx_cursor c ) {
  902. DestroyIcon(c);
  903. }
  904. HL_PRIM void HL_NAME(set_cursor)( dx_cursor c ) {
  905. cur_cursor = c;
  906. if( CURSOR_VISIBLE )
  907. SetCursor(c);
  908. }
  909. HL_PRIM void HL_NAME(show_cursor)( bool visible ) {
  910. show_cursor = visible;
  911. SetCursor(CURSOR_VISIBLE ? cur_cursor : NULL);
  912. }
  913. HL_PRIM bool HL_NAME(is_cursor_visible)() {
  914. return show_cursor;
  915. }
  916. #define TCURSOR _ABSTRACT(dx_cursor)
  917. DEFINE_PRIM(TCURSOR, load_cursor, _I32);
  918. DEFINE_PRIM(TCURSOR, create_cursor, _I32 _I32 _BYTES _I32 _I32);
  919. DEFINE_PRIM(_VOID, destroy_cursor, TCURSOR);
  920. DEFINE_PRIM(_VOID, set_cursor, TCURSOR);
  921. DEFINE_PRIM(_VOID, show_cursor, _BOOL);
  922. DEFINE_PRIM(_BOOL, is_cursor_visible, _NO_ARG);
  923. HL_PRIM vbyte *HL_NAME(detect_keyboard_layout)() {
  924. char q = MapVirtualKey(0x10, MAPVK_VSC_TO_VK);
  925. char w = MapVirtualKey(0x11, MAPVK_VSC_TO_VK);
  926. char y = MapVirtualKey(0x15, MAPVK_VSC_TO_VK);
  927. if (q == 'Q' && w == 'W' && y == 'Y') return "qwerty";
  928. if (q == 'A' && w == 'Z' && y == 'Y') return "azerty";
  929. if (q == 'Q' && w == 'W' && y == 'Z') return "qwertz";
  930. if (q == 'Q' && w == 'Z' && y == 'Y') return "qzerty";
  931. return "unknown";
  932. }
  933. DEFINE_PRIM(_BYTES, detect_keyboard_layout, _NO_ARG);