# HG changeset patch # User sam # Date 1740801227 -25200 # Node ID ee9bc0f47f41a9b8811c00a3db69b1703a4420ce # Parent 8ec9ce8eade03669fb7636cf9878943918262cf8 did: cleanup platform code diff -r 8ec9ce8eade0 -r ee9bc0f47f41 semicongine/platform/linux/rendering.nim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/semicongine/platform/linux/rendering.nim Sat Mar 01 10:53:47 2025 +0700 @@ -0,0 +1,306 @@ +import std/options +import std/tables + +import ../../thirdparty/x11/xlib +import ../../thirdparty/x11/xutil +import ../../thirdparty/x11/x as x11 +import ../../thirdparty/x11/xkblib + +const REQUIRED_PLATFORM_EXTENSIONS = @["VK_KHR_xlib_surface"] + +# got values (keycodes) from xev +const KeyTypeMap = { + 9: Escape, + 67: F1, + 68: F2, + 69: F3, + 70: F4, + 71: F5, + 72: F6, + 73: F7, + 74: F8, + 75: F9, + 76: F10, + 95: F11, + 96: F12, + 49: NumberRowExtra1, + 10: `1`, + 11: `2`, + 12: `3`, + 13: `4`, + 14: `5`, + 15: `6`, + 16: `7`, + 17: `8`, + 18: `9`, + 19: `0`, + 20: NumberRowExtra2, + 21: NumberRowExtra3, + 24: Q, + 25: W, + 26: E, + 27: Key.R, + 28: T, + 29: Key.Y, + 30: U, + 31: I, + 32: Key.O, + 33: P, + 38: A, + 39: S, + 40: D, + 41: Key.F, + 42: Key.G, + 43: H, + 44: J, + 45: K, + 46: L, + 52: Key.Z, + 53: Key.X, + 54: C, + 55: V, + 56: Key.B, + 57: N, + 58: M, + 23: Tab, + 66: CapsLock, + 50: ShiftL, + 62: ShiftR, + 37: CtrlL, + 105: CtrlR, + 133: SuperL, + 134: SuperR, + 64: AltL, + 108: AltR, + 65: Space, + 36: Enter, + 22: Backspace, + 34: LetterRow1Extra1, + 35: LetterRow1Extra2, + 47: LetterRow2Extra1, + 48: LetterRow2Extra2, + 51: LetterRow2Extra3, + 59: LetterRow3Extra1, + 60: LetterRow3Extra2, + 61: LetterRow3Extra3, + 111: Up, + 116: Down, + 113: Key.Left, + 114: Key.Right, + 112: PageUp, + 117: PageDown, + 110: Home, + 115: End, + 118: Insert, + 119: Delete, + 107: PrintScreen, + 78: ScrollLock, + 127: Pause, +}.toTable + +const MouseButtonTypeMap = { + x11.Button1: MouseButton.Mouse1, + x11.Button2: MouseButton.Mouse2, + x11.Button3: MouseButton.Mouse3, +}.toTable + +var deleteMessage* {.hint[GlobalVar]: off.}: Atom # one internal use, not serious + +template checkXlibResult(call: untyped) = + let value = call + if value == 0: + raise + newException(Exception, "Xlib error: " & astToStr(call) & " returned " & $value) + +proc XErrorLogger(display: PDisplay, event: PXErrorEvent): cint {.cdecl.} = + logging.error &"Xlib: {event[]}" + +proc createWindow*(title: string): NativeWindow = + checkXlibResult XInitThreads() + let display = XOpenDisplay(nil) + if display == nil: + quit "Failed to open display" + discard XSetErrorHandler(XErrorLogger) + + let rootWindow = display.XDefaultRootWindow() + discard display.XkbSetDetectableAutoRepeat(true, nil) + var + attribs: XWindowAttributes + width = cuint(800) + height = cuint(600) + checkXlibResult display.XGetWindowAttributes(rootWindow, addr(attribs)) + + var attrs = XSetWindowAttributes() + let window = XCreateWindow( + display, + rootWindow, + (attribs.width - cint(width)) div 2, + (attribs.height - cint(height)) div 2, + width, + height, + 0, + CopyFromParent, + InputOutput, + cast[PVisual](CopyFromParent), + 0, # CWOverrideRedirect, + addr attrs, # foregroundColor, backgroundColor + ) + checkXlibResult XSetStandardProperties( + display, window, title, "window", 0, nil, 0, nil + ) + checkXlibResult XSelectInput( + display, + window, + ButtonPressMask or ButtonReleaseMask or KeyPressMask or KeyReleaseMask or + ExposureMask or FocusChangeMask, + ) + checkXlibResult XMapWindow(display, window) + + deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false)) + checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1) + + var data = "\0".cstring + var pixmap = display.XCreateBitmapFromData(window, data, 1, 1) + var color: XColor + var empty_cursor = + display.XCreatePixmapCursor(pixmap, pixmap, addr(color), addr(color), 0, 0) + checkXlibResult display.XFreePixmap(pixmap) + return NativeWindow(display: display, window: window, emptyCursor: empty_cursor) + +proc destroyWindow*(window: NativeWindow) = + checkXlibResult XDestroyWindow(window.display, window.window) + +proc setTitle*(window: NativeWindow, title: string) = + discard XSetStandardProperties( + window.display, window.window, title, "window", 0, nil, 0, nil + ) + +proc setFullscreen*(window: var NativeWindow, enable: bool) = + var + wm_state = window.display.XInternAtom("_NET_WM_STATE", 0) + wm_fullscreen = window.display.XInternAtom("_NET_WM_STATE_FULLSCREEN", 0) + var xev: XEvent + xev.xclient = XClientMessageEvent( + message_type: wm_state, + format: 32, + window: window.window, + data: XClientMessageData(l: [int(not enable) xor 1, clong(wm_fullscreen), 0, 0, 0]), + ) + xev.theType = ClientMessage + + checkXlibResult window.display.XSendEvent( + window.display.DefaultRootWindow(), + 0, + SubstructureRedirectMask or SubstructureNotifyMask, + addr xev, + ) + checkXlibResult window.display.XFlush() + +proc showSystemCursor*(window: NativeWindow, value: bool) = + if value == true: + checkXlibResult XUndefineCursor(window.display, window.window) + checkXlibResult window.display.XFlush() + else: + checkXlibResult XDefineCursor(window.display, window.window, window.emptyCursor) + checkXlibResult window.display.XFlush() + +proc destroy*(window: NativeWindow) = + checkXlibResult window.display.XFreeCursor(window.emptyCursor) + checkXlibResult window.display.XDestroyWindow(window.window) + discard window.display.XCloseDisplay() # always returns 0 + +proc size*(window: NativeWindow): Vec2i = + var attribs: XWindowAttributes + discard XGetWindowAttributes(window.display, window.window, addr(attribs)) + vec2i(attribs.width, attribs.height) + +proc pendingEvents*(window: NativeWindow): seq[Event] = + var event: XEvent + while window.display.XPending() > 0: + discard window.display.XNextEvent(addr(event)) + case event.theType + of ClientMessage: + if cast[Atom](event.xclient.data.l[0]) == deleteMessage: + result.add(Event(eventType: Quit)) + of KeyPress: + let keyevent = cast[PXKeyEvent](addr(event)) + let xkey = int(keyevent.keycode) + result.add Event( + eventType: KeyPressed, key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN) + ) + of KeyRelease: + let keyevent = cast[PXKeyEvent](addr(event)) + let xkey = int(keyevent.keycode) + result.add Event( + eventType: KeyReleased, key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN) + ) + of ButtonPress: + let button = int(cast[PXButtonEvent](addr(event)).button) + if button == Button4: + result.add Event(eventType: MouseWheel, amount: 1'f32) + elif button == Button5: + result.add Event(eventType: MouseWheel, amount: -1'f32) + else: + result.add Event( + eventType: MousePressed, + button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN), + ) + of ButtonRelease: + let button = int(cast[PXButtonEvent](addr(event)).button) + result.add Event( + eventType: MouseReleased, + button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN), + ) + of FocusIn: + result.add Event(eventType: GotFocus) + of FocusOut: + result.add Event(eventType: LostFocus) + of ConfigureNotify, Expose: + result.add Event(eventType: ResizedWindow) + else: + discard + +proc getMousePosition*(window: NativeWindow): Vec2i = + var + root: x11.Window + win: x11.Window + rootX: cint + rootY: cint + winX: cint + winY: cint + mask: cuint + discard XQueryPointer( + window.display, + window.window, + addr(root), + addr(win), + addr(rootX), + addr(rootY), + addr(winX), + addr(winY), + addr(mask), + ) + vec2i(winX, winY) + +proc setMousePosition*(window: NativeWindow, pos: Vec2i) = + discard XWarpPointer( + window.display, + default(x11.Window), + window.window, + 0, + 0, + 0, + 0, + pos.x.cint, + pos.y.cint, + ) + +proc createNativeSurface(instance: VkInstance, window: NativeWindow): VkSurfaceKHR = + var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR( + sType: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + dpy: cast[ptr core.Display](window.display), + window: cast[core.Window](window.window), + ) + checkVkResult vkCreateXlibSurfaceKHR( + instance, addr(surfaceCreateInfo), nil, addr(result) + ) diff -r 8ec9ce8eade0 -r ee9bc0f47f41 semicongine/platform/windows/rendering.nim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/semicongine/platform/windows/rendering.nim Sat Mar 01 10:53:47 2025 +0700 @@ -0,0 +1,308 @@ +import std/tables +import std/options + +import ../../thirdparty/winim/winim/inc/[windef, winuser, wincon, winbase] +import ../../thirdparty/winim/winim/[winstr, utils] + +const REQUIRED_PLATFORM_EXTENSIONS = @["VK_KHR_win32_surface"] + +const KeyTypeMap* = { + VK_ESCAPE: Key.Escape, + VK_F1: F1, + VK_F2: F2, + VK_F3: F3, + VK_F4: F4, + VK_F5: F5, + VK_F6: F6, + VK_F7: F7, + VK_F8: F8, + VK_F9: F9, + VK_F10: F10, + VK_F11: F11, + VK_F12: F12, + VK_OEM_3: NumberRowExtra1, + int('0'): `0`, + int('1'): `1`, + int('2'): `2`, + int('3'): `3`, + int('4'): `4`, + int('5'): `5`, + int('6'): `6`, + int('7'): `7`, + int('8'): `8`, + int('9'): `9`, + VK_OEM_MINUS: NumberRowExtra2, + VK_OEM_PLUS: NumberRowExtra3, + int('A'): A, + int('B'): Key.B, + int('C'): C, + int('D'): D, + int('E'): E, + int('F'): F, + int('G'): Key.G, + int('H'): H, + int('I'): I, + int('J'): J, + int('K'): K, + int('L'): Key.L, + int('M'): M, + int('N'): N, + int('O'): Key.O, + int('P'): P, + int('Q'): Q, + int('R'): Key.R, + int('S'): S, + int('T'): Key.T, + int('U'): U, + int('V'): V, + int('W'): W, + int('X'): Key.X, + int('Y'): Key.Y, + int('Z'): Key.Z, + VK_TAB: Tab, + VK_CAPITAL: CapsLock, + VK_LSHIFT: ShiftL, + VK_SHIFT: ShiftL, + VK_RSHIFT: ShiftR, + VK_LCONTROL: CtrlL, + VK_CONTROL: CtrlL, + VK_RCONTROL: CtrlR, + VK_LWIN: SuperL, + VK_RWIN: SuperR, + VK_LMENU: AltL, + VK_RMENU: AltR, + VK_SPACE: Space, + VK_RETURN: Enter, + VK_BACK: Backspace, + VK_OEM_4: LetterRow1Extra1, + VK_OEM_6: LetterRow1Extra2, + VK_OEM_5: LetterRow2Extra3, + VK_OEM_1: LetterRow2Extra1, + VK_OEM_7: LetterRow2Extra2, + VK_OEM_COMMA: LetterRow3Extra1, + VK_OEM_PERIOD: LetterRow3Extra2, + VK_OEM_2: LetterRow3Extra3, + VK_UP: Up, + VK_DOWN: Down, + VK_LEFT: Key.Left, + VK_RIGHT: Key.Right, + VK_PRIOR: PageUp, + VK_NEXT: PageDown, + VK_HOME: Home, + VK_END: End, + VK_INSERT: Insert, + VK_DELETE: Key.Delete, +}.toTable + +# sorry, have to use module-global variable to capture windows events +var currentEvents: seq[Event] + +template checkWin32Result*(call: untyped) = + let value = call + if value == 0: + raise + newException(Exception, "Win32 error: " & astToStr(call) & " returned " & $value) + +let + andCursorMask = [0xff] + xorCursorMask = [0x00] + invisibleCursor = CreateCursor( + 0, 0, 0, 1, 1, pointer(addr andCursorMask), pointer(addr xorCursorMask) + ) + defaultCursor = LoadCursor(0, IDC_ARROW) +var currentCursor = defaultCursor + +proc mapLeftRightKeys(key: INT, lparam: LPARAM): INT = + case key + of VK_SHIFT: + MapVirtualKey(UINT((lParam and 0x00ff0000) shr 16), MAPVK_VSC_TO_VK_EX) + of VK_CONTROL: + if (lParam and 0x01000000) == 0: VK_LCONTROL else: VK_RCONTROL + of VK_MENU: + if (lParam and 0x01000000) == 0: VK_LMENU else: VK_RMENU + else: + key + +proc windowHandler( + hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM +): LRESULT {.stdcall.} = + case uMsg + of WM_DESTROY: + currentEvents.add(Event(eventType: Quit)) + of WM_KEYDOWN, WM_SYSKEYDOWN: + let key = mapLeftRightKeys(INT(wParam), lParam) + currentEvents.add( + Event(eventType: KeyPressed, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN)) + ) + of WM_KEYUP, WM_SYSKEYUP: + let key = mapLeftRightKeys(INT(wParam), lParam) + currentEvents.add( + Event(eventType: KeyReleased, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN)) + ) + of WM_LBUTTONDOWN: + currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse1)) + of WM_LBUTTONUP: + currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse1)) + of WM_MBUTTONDOWN: + currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse2)) + of WM_MBUTTONUP: + currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse2)) + of WM_RBUTTONDOWN: + currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse3)) + of WM_RBUTTONUP: + currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse3)) + of WM_MOUSEWHEEL: + currentEvents.add( + Event( + eventType: MouseWheel, + amount: float32(GET_WHEEL_DELTA_WPARAM(wParam)) / WHEEL_DELTA, + ) + ) + of WM_SIZING: + currentEvents.add(Event(eventType: ResizedWindow)) + of WM_SIZE: + if wParam == SIZE_MINIMIZED: + currentEvents.add(Event(eventType: MinimizedWindow)) + elif wParam == SIZE_RESTORED: + currentEvents.add(Event(eventType: RestoredWindow)) + of WM_SETFOCUS: + currentEvents.add(Event(eventType: GotFocus)) + of WM_KILLFOCUS: + currentEvents.add(Event(eventType: LostFocus)) + of WM_SETCURSOR: + if LOWORD(lParam) == HTCLIENT: + SetCursor(currentCursor) + return 1 + else: + return DefWindowProc(hwnd, uMsg, wParam, lParam) + else: + return DefWindowProc(hwnd, uMsg, wParam, lParam) + +proc createWindow*(title: string): NativeWindow = + when not defined(release): + AllocConsole() + discard stdin.reopen("conIN$", fmRead) + discard stdout.reopen("conOUT$", fmWrite) + discard stderr.reopen("conOUT$", fmWrite) + + result.hInstance = HINSTANCE(GetModuleHandle(nil)) + var + windowClassName = T"EngineWindowClass" + windowName = T(title) + windowClass = WNDCLASSEX( + cbSize: UINT(WNDCLASSEX.sizeof), + lpfnWndProc: windowHandler, + hInstance: result.hInstance, + lpszClassName: windowClassName, + hcursor: currentCursor, + ) + + if (RegisterClassEx(addr(windowClass)) == 0): + raise newException(Exception, "Unable to register window class") + + result.hwnd = CreateWindowEx( + DWORD(0), + windowClassName, + windowName, + DWORD(WS_OVERLAPPEDWINDOW), + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + HMENU(0), + HINSTANCE(0), + result.hInstance, + nil, + ) + + result.g_wpPrev.length = UINT(sizeof(WINDOWPLACEMENT)) + discard result.hwnd.ShowWindow(SW_SHOW) + discard result.hwnd.SetForegroundWindow() + discard result.hwnd.SetFocus() + +proc destroyWindow*(window: NativeWindow) = + DestroyWindow(window.hwnd) + +proc setTitle*(window: NativeWindow, title: string) = + window.hwnd.SetWindowText(T(title)) + +# inspired by the one and only, Raymond Chen +# https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353 +proc setFullscreen*(window: var NativeWindow, enable: bool) = + let dwStyle: DWORD = GetWindowLong(window.hwnd, GWL_STYLE) + if enable: + var mi = MONITORINFO(cbSize: DWORD(sizeof(MONITORINFO))) + if GetWindowPlacement(window.hwnd, addr window.g_wpPrev) and + GetMonitorInfo( + MonitorFromWindow(window.hwnd, MONITOR_DEFAULTTOPRIMARY), addr mi + ): + SetWindowLong(window.hwnd, GWL_STYLE, dwStyle and (not WS_OVERLAPPEDWINDOW)) + SetWindowPos( + window.hwnd, + HWND_TOP, + mi.rcMonitor.left, + mi.rcMonitor.top, + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + SWP_NOOWNERZORDER or SWP_FRAMECHANGED, + ) + else: + SetWindowLong(window.hwnd, GWL_STYLE, dwStyle or WS_OVERLAPPEDWINDOW) + SetWindowPlacement(window.hwnd, addr window.g_wpPrev) + SetWindowPos( + window.hwnd, + HWND(0), + 0, + 0, + 0, + 0, + SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER or SWP_NOOWNERZORDER or SWP_FRAMECHANGED, + ) + +proc showSystemCursor*(window: NativeWindow, value: bool) = + if value == true: + currentCursor = defaultCursor + SetCursor(currentCursor) + else: + currentCursor = invisibleCursor + SetCursor(currentCursor) + +proc destroy*(window: NativeWindow) = + discard + +proc size*(window: NativeWindow): Vec2i = + var rect: RECT + discard GetClientRect(window.hwnd, addr(rect)) + vec2i(rect.right - rect.left, rect.bottom - rect.top) + +proc pendingEvents*(window: NativeWindow): seq[Event] = + # empty queue + var msg: MSG + # fill queue + while PeekMessage(addr(msg), window.hwnd, 0, 0, PM_REMOVE): + TranslateMessage(addr(msg)) + DispatchMessage(addr(msg)) + result = currentEvents + currentEvents.setLen(0) + +proc getMousePosition*(window: NativeWindow): Vec2i = + var p: POINT + discard GetCursorPos(addr(p)) + discard window.hwnd.ScreenToClient(addr(p)) + vec2i(p.x, p.y) + +proc setMousePosition*(window: NativeWindow, pos: Vec2i) = + var p = POINT(x: pos.x, y: pos.y) + discard window.hwnd.ClientToScreen(addr(p)) + discard SetCursorPos(p.x, p.y) + +proc createNativeSurface*(instance: VkInstance, window: NativeWindow): VkSurfaceKHR = + assert instance.Valid + var surfaceCreateInfo = VkWin32SurfaceCreateInfoKHR( + sType: VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, + hinstance: cast[HINSTANCE](window.hinstance), + hwnd: cast[HWND](window.hwnd), + ) + checkVkResult vkCreateWin32SurfaceKHR( + instance, addr(surfaceCreateInfo), nil, addr(result) + ) diff -r 8ec9ce8eade0 -r ee9bc0f47f41 semicongine/rendering.nim --- a/semicongine/rendering.nim Thu Feb 27 20:17:57 2025 +0700 +++ b/semicongine/rendering.nim Sat Mar 01 10:53:47 2025 +0700 @@ -22,9 +22,9 @@ # believe me, this makes everything much, much easier when defined(windows): - include ./rendering/platform/windows + include ./platform/windows/rendering when defined(linux): - include ./rendering/platform/linux + include ./platform/linux/rendering import ../semicongine/rendering/memory import ../semicongine/rendering/renderer diff -r 8ec9ce8eade0 -r ee9bc0f47f41 semicongine/rendering/platform/linux.nim --- a/semicongine/rendering/platform/linux.nim Thu Feb 27 20:17:57 2025 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,306 +0,0 @@ -import std/options -import std/tables - -import ../../thirdparty/x11/xlib -import ../../thirdparty/x11/xutil -import ../../thirdparty/x11/x as x11 -import ../../thirdparty/x11/xkblib - -const REQUIRED_PLATFORM_EXTENSIONS = @["VK_KHR_xlib_surface"] - -# got values (keycodes) from xev -const KeyTypeMap = { - 9: Escape, - 67: F1, - 68: F2, - 69: F3, - 70: F4, - 71: F5, - 72: F6, - 73: F7, - 74: F8, - 75: F9, - 76: F10, - 95: F11, - 96: F12, - 49: NumberRowExtra1, - 10: `1`, - 11: `2`, - 12: `3`, - 13: `4`, - 14: `5`, - 15: `6`, - 16: `7`, - 17: `8`, - 18: `9`, - 19: `0`, - 20: NumberRowExtra2, - 21: NumberRowExtra3, - 24: Q, - 25: W, - 26: E, - 27: Key.R, - 28: T, - 29: Key.Y, - 30: U, - 31: I, - 32: Key.O, - 33: P, - 38: A, - 39: S, - 40: D, - 41: Key.F, - 42: Key.G, - 43: H, - 44: J, - 45: K, - 46: L, - 52: Key.Z, - 53: Key.X, - 54: C, - 55: V, - 56: Key.B, - 57: N, - 58: M, - 23: Tab, - 66: CapsLock, - 50: ShiftL, - 62: ShiftR, - 37: CtrlL, - 105: CtrlR, - 133: SuperL, - 134: SuperR, - 64: AltL, - 108: AltR, - 65: Space, - 36: Enter, - 22: Backspace, - 34: LetterRow1Extra1, - 35: LetterRow1Extra2, - 47: LetterRow2Extra1, - 48: LetterRow2Extra2, - 51: LetterRow2Extra3, - 59: LetterRow3Extra1, - 60: LetterRow3Extra2, - 61: LetterRow3Extra3, - 111: Up, - 116: Down, - 113: Key.Left, - 114: Key.Right, - 112: PageUp, - 117: PageDown, - 110: Home, - 115: End, - 118: Insert, - 119: Delete, - 107: PrintScreen, - 78: ScrollLock, - 127: Pause, -}.toTable - -const MouseButtonTypeMap = { - x11.Button1: MouseButton.Mouse1, - x11.Button2: MouseButton.Mouse2, - x11.Button3: MouseButton.Mouse3, -}.toTable - -var deleteMessage* {.hint[GlobalVar]: off.}: Atom # one internal use, not serious - -template checkXlibResult(call: untyped) = - let value = call - if value == 0: - raise - newException(Exception, "Xlib error: " & astToStr(call) & " returned " & $value) - -proc XErrorLogger(display: PDisplay, event: PXErrorEvent): cint {.cdecl.} = - logging.error &"Xlib: {event[]}" - -proc createWindow*(title: string): NativeWindow = - checkXlibResult XInitThreads() - let display = XOpenDisplay(nil) - if display == nil: - quit "Failed to open display" - discard XSetErrorHandler(XErrorLogger) - - let rootWindow = display.XDefaultRootWindow() - discard display.XkbSetDetectableAutoRepeat(true, nil) - var - attribs: XWindowAttributes - width = cuint(800) - height = cuint(600) - checkXlibResult display.XGetWindowAttributes(rootWindow, addr(attribs)) - - var attrs = XSetWindowAttributes() - let window = XCreateWindow( - display, - rootWindow, - (attribs.width - cint(width)) div 2, - (attribs.height - cint(height)) div 2, - width, - height, - 0, - CopyFromParent, - InputOutput, - cast[PVisual](CopyFromParent), - 0, # CWOverrideRedirect, - addr attrs, # foregroundColor, backgroundColor - ) - checkXlibResult XSetStandardProperties( - display, window, title, "window", 0, nil, 0, nil - ) - checkXlibResult XSelectInput( - display, - window, - ButtonPressMask or ButtonReleaseMask or KeyPressMask or KeyReleaseMask or - ExposureMask or FocusChangeMask, - ) - checkXlibResult XMapWindow(display, window) - - deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false)) - checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1) - - var data = "\0".cstring - var pixmap = display.XCreateBitmapFromData(window, data, 1, 1) - var color: XColor - var empty_cursor = - display.XCreatePixmapCursor(pixmap, pixmap, addr(color), addr(color), 0, 0) - checkXlibResult display.XFreePixmap(pixmap) - return NativeWindow(display: display, window: window, emptyCursor: empty_cursor) - -proc destroyWindow*(window: NativeWindow) = - checkXlibResult XDestroyWindow(window.display, window.window) - -proc setTitle*(window: NativeWindow, title: string) = - discard XSetStandardProperties( - window.display, window.window, title, "window", 0, nil, 0, nil - ) - -proc setFullscreen*(window: var NativeWindow, enable: bool) = - var - wm_state = window.display.XInternAtom("_NET_WM_STATE", 0) - wm_fullscreen = window.display.XInternAtom("_NET_WM_STATE_FULLSCREEN", 0) - var xev: XEvent - xev.xclient = XClientMessageEvent( - message_type: wm_state, - format: 32, - window: window.window, - data: XClientMessageData(l: [int(not enable) xor 1, clong(wm_fullscreen), 0, 0, 0]), - ) - xev.theType = ClientMessage - - checkXlibResult window.display.XSendEvent( - window.display.DefaultRootWindow(), - 0, - SubstructureRedirectMask or SubstructureNotifyMask, - addr xev, - ) - checkXlibResult window.display.XFlush() - -proc showSystemCursor*(window: NativeWindow, value: bool) = - if value == true: - checkXlibResult XUndefineCursor(window.display, window.window) - checkXlibResult window.display.XFlush() - else: - checkXlibResult XDefineCursor(window.display, window.window, window.emptyCursor) - checkXlibResult window.display.XFlush() - -proc destroy*(window: NativeWindow) = - checkXlibResult window.display.XFreeCursor(window.emptyCursor) - checkXlibResult window.display.XDestroyWindow(window.window) - discard window.display.XCloseDisplay() # always returns 0 - -proc size*(window: NativeWindow): Vec2i = - var attribs: XWindowAttributes - discard XGetWindowAttributes(window.display, window.window, addr(attribs)) - vec2i(attribs.width, attribs.height) - -proc pendingEvents*(window: NativeWindow): seq[Event] = - var event: XEvent - while window.display.XPending() > 0: - discard window.display.XNextEvent(addr(event)) - case event.theType - of ClientMessage: - if cast[Atom](event.xclient.data.l[0]) == deleteMessage: - result.add(Event(eventType: Quit)) - of KeyPress: - let keyevent = cast[PXKeyEvent](addr(event)) - let xkey = int(keyevent.keycode) - result.add Event( - eventType: KeyPressed, key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN) - ) - of KeyRelease: - let keyevent = cast[PXKeyEvent](addr(event)) - let xkey = int(keyevent.keycode) - result.add Event( - eventType: KeyReleased, key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN) - ) - of ButtonPress: - let button = int(cast[PXButtonEvent](addr(event)).button) - if button == Button4: - result.add Event(eventType: MouseWheel, amount: 1'f32) - elif button == Button5: - result.add Event(eventType: MouseWheel, amount: -1'f32) - else: - result.add Event( - eventType: MousePressed, - button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN), - ) - of ButtonRelease: - let button = int(cast[PXButtonEvent](addr(event)).button) - result.add Event( - eventType: MouseReleased, - button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN), - ) - of FocusIn: - result.add Event(eventType: GotFocus) - of FocusOut: - result.add Event(eventType: LostFocus) - of ConfigureNotify, Expose: - result.add Event(eventType: ResizedWindow) - else: - discard - -proc getMousePosition*(window: NativeWindow): Vec2i = - var - root: x11.Window - win: x11.Window - rootX: cint - rootY: cint - winX: cint - winY: cint - mask: cuint - discard XQueryPointer( - window.display, - window.window, - addr(root), - addr(win), - addr(rootX), - addr(rootY), - addr(winX), - addr(winY), - addr(mask), - ) - vec2i(winX, winY) - -proc setMousePosition*(window: NativeWindow, pos: Vec2i) = - discard XWarpPointer( - window.display, - default(x11.Window), - window.window, - 0, - 0, - 0, - 0, - pos.x.cint, - pos.y.cint, - ) - -proc createNativeSurface(instance: VkInstance, window: NativeWindow): VkSurfaceKHR = - var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR( - sType: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, - dpy: cast[ptr core.Display](window.display), - window: cast[core.Window](window.window), - ) - checkVkResult vkCreateXlibSurfaceKHR( - instance, addr(surfaceCreateInfo), nil, addr(result) - ) diff -r 8ec9ce8eade0 -r ee9bc0f47f41 semicongine/rendering/platform/windows.nim --- a/semicongine/rendering/platform/windows.nim Thu Feb 27 20:17:57 2025 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,308 +0,0 @@ -import std/tables -import std/options - -import ../../thirdparty/winim/winim/inc/[windef, winuser, wincon, winbase] -import ../../thirdparty/winim/winim/[winstr, utils] - -const REQUIRED_PLATFORM_EXTENSIONS = @["VK_KHR_win32_surface"] - -const KeyTypeMap* = { - VK_ESCAPE: Key.Escape, - VK_F1: F1, - VK_F2: F2, - VK_F3: F3, - VK_F4: F4, - VK_F5: F5, - VK_F6: F6, - VK_F7: F7, - VK_F8: F8, - VK_F9: F9, - VK_F10: F10, - VK_F11: F11, - VK_F12: F12, - VK_OEM_3: NumberRowExtra1, - int('0'): `0`, - int('1'): `1`, - int('2'): `2`, - int('3'): `3`, - int('4'): `4`, - int('5'): `5`, - int('6'): `6`, - int('7'): `7`, - int('8'): `8`, - int('9'): `9`, - VK_OEM_MINUS: NumberRowExtra2, - VK_OEM_PLUS: NumberRowExtra3, - int('A'): A, - int('B'): Key.B, - int('C'): C, - int('D'): D, - int('E'): E, - int('F'): F, - int('G'): Key.G, - int('H'): H, - int('I'): I, - int('J'): J, - int('K'): K, - int('L'): Key.L, - int('M'): M, - int('N'): N, - int('O'): Key.O, - int('P'): P, - int('Q'): Q, - int('R'): Key.R, - int('S'): S, - int('T'): Key.T, - int('U'): U, - int('V'): V, - int('W'): W, - int('X'): Key.X, - int('Y'): Key.Y, - int('Z'): Key.Z, - VK_TAB: Tab, - VK_CAPITAL: CapsLock, - VK_LSHIFT: ShiftL, - VK_SHIFT: ShiftL, - VK_RSHIFT: ShiftR, - VK_LCONTROL: CtrlL, - VK_CONTROL: CtrlL, - VK_RCONTROL: CtrlR, - VK_LWIN: SuperL, - VK_RWIN: SuperR, - VK_LMENU: AltL, - VK_RMENU: AltR, - VK_SPACE: Space, - VK_RETURN: Enter, - VK_BACK: Backspace, - VK_OEM_4: LetterRow1Extra1, - VK_OEM_6: LetterRow1Extra2, - VK_OEM_5: LetterRow2Extra3, - VK_OEM_1: LetterRow2Extra1, - VK_OEM_7: LetterRow2Extra2, - VK_OEM_COMMA: LetterRow3Extra1, - VK_OEM_PERIOD: LetterRow3Extra2, - VK_OEM_2: LetterRow3Extra3, - VK_UP: Up, - VK_DOWN: Down, - VK_LEFT: Key.Left, - VK_RIGHT: Key.Right, - VK_PRIOR: PageUp, - VK_NEXT: PageDown, - VK_HOME: Home, - VK_END: End, - VK_INSERT: Insert, - VK_DELETE: Key.Delete, -}.toTable - -# sorry, have to use module-global variable to capture windows events -var currentEvents: seq[Event] - -template checkWin32Result*(call: untyped) = - let value = call - if value == 0: - raise - newException(Exception, "Win32 error: " & astToStr(call) & " returned " & $value) - -let - andCursorMask = [0xff] - xorCursorMask = [0x00] - invisibleCursor = CreateCursor( - 0, 0, 0, 1, 1, pointer(addr andCursorMask), pointer(addr xorCursorMask) - ) - defaultCursor = LoadCursor(0, IDC_ARROW) -var currentCursor = defaultCursor - -proc mapLeftRightKeys(key: INT, lparam: LPARAM): INT = - case key - of VK_SHIFT: - MapVirtualKey(UINT((lParam and 0x00ff0000) shr 16), MAPVK_VSC_TO_VK_EX) - of VK_CONTROL: - if (lParam and 0x01000000) == 0: VK_LCONTROL else: VK_RCONTROL - of VK_MENU: - if (lParam and 0x01000000) == 0: VK_LMENU else: VK_RMENU - else: - key - -proc windowHandler( - hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM -): LRESULT {.stdcall.} = - case uMsg - of WM_DESTROY: - currentEvents.add(Event(eventType: Quit)) - of WM_KEYDOWN, WM_SYSKEYDOWN: - let key = mapLeftRightKeys(INT(wParam), lParam) - currentEvents.add( - Event(eventType: KeyPressed, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN)) - ) - of WM_KEYUP, WM_SYSKEYUP: - let key = mapLeftRightKeys(INT(wParam), lParam) - currentEvents.add( - Event(eventType: KeyReleased, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN)) - ) - of WM_LBUTTONDOWN: - currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse1)) - of WM_LBUTTONUP: - currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse1)) - of WM_MBUTTONDOWN: - currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse2)) - of WM_MBUTTONUP: - currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse2)) - of WM_RBUTTONDOWN: - currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse3)) - of WM_RBUTTONUP: - currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse3)) - of WM_MOUSEWHEEL: - currentEvents.add( - Event( - eventType: MouseWheel, - amount: float32(GET_WHEEL_DELTA_WPARAM(wParam)) / WHEEL_DELTA, - ) - ) - of WM_SIZING: - currentEvents.add(Event(eventType: ResizedWindow)) - of WM_SIZE: - if wParam == SIZE_MINIMIZED: - currentEvents.add(Event(eventType: MinimizedWindow)) - elif wParam == SIZE_RESTORED: - currentEvents.add(Event(eventType: RestoredWindow)) - of WM_SETFOCUS: - currentEvents.add(Event(eventType: GotFocus)) - of WM_KILLFOCUS: - currentEvents.add(Event(eventType: LostFocus)) - of WM_SETCURSOR: - if LOWORD(lParam) == HTCLIENT: - SetCursor(currentCursor) - return 1 - else: - return DefWindowProc(hwnd, uMsg, wParam, lParam) - else: - return DefWindowProc(hwnd, uMsg, wParam, lParam) - -proc createWindow*(title: string): NativeWindow = - when not defined(release): - AllocConsole() - discard stdin.reopen("conIN$", fmRead) - discard stdout.reopen("conOUT$", fmWrite) - discard stderr.reopen("conOUT$", fmWrite) - - result.hInstance = HINSTANCE(GetModuleHandle(nil)) - var - windowClassName = T"EngineWindowClass" - windowName = T(title) - windowClass = WNDCLASSEX( - cbSize: UINT(WNDCLASSEX.sizeof), - lpfnWndProc: windowHandler, - hInstance: result.hInstance, - lpszClassName: windowClassName, - hcursor: currentCursor, - ) - - if (RegisterClassEx(addr(windowClass)) == 0): - raise newException(Exception, "Unable to register window class") - - result.hwnd = CreateWindowEx( - DWORD(0), - windowClassName, - windowName, - DWORD(WS_OVERLAPPEDWINDOW), - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - HMENU(0), - HINSTANCE(0), - result.hInstance, - nil, - ) - - result.g_wpPrev.length = UINT(sizeof(WINDOWPLACEMENT)) - discard result.hwnd.ShowWindow(SW_SHOW) - discard result.hwnd.SetForegroundWindow() - discard result.hwnd.SetFocus() - -proc destroyWindow*(window: NativeWindow) = - DestroyWindow(window.hwnd) - -proc setTitle*(window: NativeWindow, title: string) = - window.hwnd.SetWindowText(T(title)) - -# inspired by the one and only, Raymond Chen -# https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353 -proc setFullscreen*(window: var NativeWindow, enable: bool) = - let dwStyle: DWORD = GetWindowLong(window.hwnd, GWL_STYLE) - if enable: - var mi = MONITORINFO(cbSize: DWORD(sizeof(MONITORINFO))) - if GetWindowPlacement(window.hwnd, addr window.g_wpPrev) and - GetMonitorInfo( - MonitorFromWindow(window.hwnd, MONITOR_DEFAULTTOPRIMARY), addr mi - ): - SetWindowLong(window.hwnd, GWL_STYLE, dwStyle and (not WS_OVERLAPPEDWINDOW)) - SetWindowPos( - window.hwnd, - HWND_TOP, - mi.rcMonitor.left, - mi.rcMonitor.top, - mi.rcMonitor.right - mi.rcMonitor.left, - mi.rcMonitor.bottom - mi.rcMonitor.top, - SWP_NOOWNERZORDER or SWP_FRAMECHANGED, - ) - else: - SetWindowLong(window.hwnd, GWL_STYLE, dwStyle or WS_OVERLAPPEDWINDOW) - SetWindowPlacement(window.hwnd, addr window.g_wpPrev) - SetWindowPos( - window.hwnd, - HWND(0), - 0, - 0, - 0, - 0, - SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER or SWP_NOOWNERZORDER or SWP_FRAMECHANGED, - ) - -proc showSystemCursor*(window: NativeWindow, value: bool) = - if value == true: - currentCursor = defaultCursor - SetCursor(currentCursor) - else: - currentCursor = invisibleCursor - SetCursor(currentCursor) - -proc destroy*(window: NativeWindow) = - discard - -proc size*(window: NativeWindow): Vec2i = - var rect: RECT - discard GetClientRect(window.hwnd, addr(rect)) - vec2i(rect.right - rect.left, rect.bottom - rect.top) - -proc pendingEvents*(window: NativeWindow): seq[Event] = - # empty queue - var msg: MSG - # fill queue - while PeekMessage(addr(msg), window.hwnd, 0, 0, PM_REMOVE): - TranslateMessage(addr(msg)) - DispatchMessage(addr(msg)) - result = currentEvents - currentEvents.setLen(0) - -proc getMousePosition*(window: NativeWindow): Vec2i = - var p: POINT - discard GetCursorPos(addr(p)) - discard window.hwnd.ScreenToClient(addr(p)) - vec2i(p.x, p.y) - -proc setMousePosition*(window: NativeWindow, pos: Vec2i) = - var p = POINT(x: pos.x, y: pos.y) - discard window.hwnd.ClientToScreen(addr(p)) - discard SetCursorPos(p.x, p.y) - -proc createNativeSurface*(instance: VkInstance, window: NativeWindow): VkSurfaceKHR = - assert instance.Valid - var surfaceCreateInfo = VkWin32SurfaceCreateInfoKHR( - sType: VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, - hinstance: cast[HINSTANCE](window.hinstance), - hwnd: cast[HWND](window.hwnd), - ) - checkVkResult vkCreateWin32SurfaceKHR( - instance, addr(surfaceCreateInfo), nil, addr(result) - )