From 1fc36a04b78e035648503be257557b7ea565b52d Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Wed, 18 Feb 2026 21:49:36 -0700 Subject: [PATCH 01/10] fix tests --- examples/text_input_demo.nim | 1 + src/siwin/platforms/wayland/sharedBuffer.nim | 75 +++++++++++++++++--- src/siwin/platforms/wayland/window.nim | 14 +++- 3 files changed, 79 insertions(+), 11 deletions(-) diff --git a/examples/text_input_demo.nim b/examples/text_input_demo.nim index e8554d5..1d5cfd7 100644 --- a/examples/text_input_demo.nim +++ b/examples/text_input_demo.nim @@ -205,6 +205,7 @@ proc pickFontPath(): string = "/usr/share/fonts/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "/usr/share/fonts/opentype/noto/NotoSans-Regular.ttf", + "/usr/local/share/fonts/dejavu/DejaVuSans.ttf" ] for path in candidates: diff --git a/src/siwin/platforms/wayland/sharedBuffer.nim b/src/siwin/platforms/wayland/sharedBuffer.nim index 89d9df8..5923e77 100644 --- a/src/siwin/platforms/wayland/sharedBuffer.nim +++ b/src/siwin/platforms/wayland/sharedBuffer.nim @@ -1,4 +1,5 @@ -import std/[memfiles, os, times, sequtils] +import std/[memfiles, os, oserrors, times, sequtils] +import std/posix import pkg/[vmath] import ../../[siwindefs] import ./[protocol, libwayland, siwinGlobals] @@ -20,6 +21,10 @@ type proc dataAddr*(buffer: SharedBuffer): pointer = + if buffer == nil: + return nil + if buffer.file.mem == nil: + return nil cast[pointer](cast[int](buffer.file.mem) + buffer.size.x * buffer.size.y * buffer.bytesPerPixel * buffer.currentBuffer.int32) proc fileDescriptor*(buffer: SharedBuffer): FileHandle = @@ -122,17 +127,69 @@ proc create*(globals: SiwinGlobalsWayland, shm: WlShm, size: IVec2, format: `WlS assert bufferCount >= 1, "at least one buffer is required" result.buffers.setLen bufferCount - let filebase = getEnv("XDG_RUNTIME_DIR") / "siwin-" - for i in 0..int.high: - if not fileExists(filebase & $i): - result.filename = filebase & $i - result.file = memfiles.open( - result.filename, mode = fmReadWrite, allowRemap = true, - newFileSize = size.x * size.y * bytesPerPixel * bufferCount.int32 + let sizeInBytes64 = int64(size.x) * int64(size.y) * int64(bytesPerPixel) * int64(bufferCount) + if sizeInBytes64 <= 0 or sizeInBytes64 > high(int32).int64: + raise ValueError.newException("invalid shared buffer size") + let sizeInBytes = sizeInBytes64.int32 + + var candidateDirs: seq[string] = @[] + let runtimeDir = getEnv("XDG_RUNTIME_DIR") + if runtimeDir.len != 0 and dirExists(runtimeDir): + candidateDirs.add runtimeDir + + let tempDir = getTempDir() + if tempDir.len != 0 and tempDir notin candidateDirs: + candidateDirs.add tempDir + + var opened = false + var openError = "" + let pid = getCurrentProcessId() + for baseDir in candidateDirs: + let filebase = baseDir / ("siwin-" & $pid & "-") + for i in 0..8192: + let filename = filebase & $i + if fileExists(filename): + continue + + let fd = posix.open( + filename, + posix.O_RDWR or posix.O_CREAT or posix.O_TRUNC or posix.O_CLOEXEC, + posix.S_IRUSR or posix.S_IWUSR ) + if fd == -1: + openError = osErrorMsg(osLastError()) + continue + if posix.ftruncate(fd, sizeInBytes) != 0: + openError = osErrorMsg(osLastError()) + discard posix.close(fd) + try: + removeFile(filename) + except OsError: + discard + continue + discard posix.close(fd) + + try: + result.file = memfiles.open(filename, mode = fmReadWrite, allowRemap = true) + result.filename = filename + opened = true + break + except CatchableError as e: + openError = e.msg + try: + if fileExists(filename): + removeFile(filename) + except OsError: + discard + if opened: break - result.pool = shm.create_pool(result.fileDescriptor, size.x * size.y * bytesPerPixel * bufferCount.int32) + if not opened: + if openError.len != 0: + raise OSError.newException("failed to create shared buffer file: " & openError) + raise OSError.newException("failed to create shared buffer file") + + result.pool = shm.create_pool(result.fileDescriptor, sizeInBytes) result.create_wl_buffers() diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index f345f6f..e1aa54c 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -325,7 +325,7 @@ method doResize(window: WindowWayland, size: IVec2) {.base.} = method doResize(window: WindowWaylandSoftwareRendering, size: IVec2) = procCall window.WindowWayland.doResize(size) - if window.buffer.locked: + if window.buffer != nil and window.buffer.locked: swap window.buffer, window.oldBuffer if window.buffer == nil: @@ -454,14 +454,24 @@ method `icon=`*(window: WindowWayland, v: PixelBuffer) = method pixelBuffer*(window: WindowWaylandSoftwareRendering): PixelBuffer = + if window.buffer == nil: + window.doResize(window.m_size) + PixelBuffer( - data: window.buffer.dataAddr, + data: (if window.buffer == nil: nil else: window.buffer.dataAddr), size: window.m_size, format: (if window.transparent: PixelBufferFormat.xrgb_32bit else: PixelBufferFormat.urgb_32bit) ) method swapBuffers(window: WindowWaylandSoftwareRendering) = + if window.buffer == nil: + if window.m_size.x <= 0 or window.m_size.y <= 0: + return + window.doResize(window.m_size) + if window.buffer == nil: + return + window.surface.attach(window.buffer.buffer, 0, 0) window.surface.damage_buffer(0, 0, window.m_size.x, window.m_size.y) commit window.surface From c86e1134f9fde239b82023a865858b083a33814d Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Wed, 18 Feb 2026 21:51:06 -0700 Subject: [PATCH 02/10] fix tests --- src/siwin/platforms/wayland/sharedBuffer.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/siwin/platforms/wayland/sharedBuffer.nim b/src/siwin/platforms/wayland/sharedBuffer.nim index 5923e77..99895c4 100644 --- a/src/siwin/platforms/wayland/sharedBuffer.nim +++ b/src/siwin/platforms/wayland/sharedBuffer.nim @@ -152,7 +152,7 @@ proc create*(globals: SiwinGlobalsWayland, shm: WlShm, size: IVec2, format: `WlS continue let fd = posix.open( - filename, + filename.cstring, posix.O_RDWR or posix.O_CREAT or posix.O_TRUNC or posix.O_CLOEXEC, posix.S_IRUSR or posix.S_IWUSR ) From 52c2d3413d14e026ecad6542e4c8f6a01ed45a06 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Wed, 18 Feb 2026 21:56:21 -0700 Subject: [PATCH 03/10] fix tests --- src/siwin/platforms/wayland/sharedBuffer.nim | 32 ++++++++++++++------ src/siwin/platforms/wayland/window.nim | 3 ++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/siwin/platforms/wayland/sharedBuffer.nim b/src/siwin/platforms/wayland/sharedBuffer.nim index 99895c4..f45e16a 100644 --- a/src/siwin/platforms/wayland/sharedBuffer.nim +++ b/src/siwin/platforms/wayland/sharedBuffer.nim @@ -41,6 +41,7 @@ proc locked*(buffer: SharedBuffer): bool = proc release*(buffer: SharedBuffer) {.raises: [Exception].} = for v in buffer.buffers.mitems: + v.locked = false if v.buffer.proxy.raw != nil: destroy v.buffer v.buffer.proxy.raw = nil @@ -72,29 +73,42 @@ proc swapBuffers*( ## if timeout reached, raises OsError. ## ! please make sure you attached current buffer to a surface before calling this proc + # Keep a strong ref because this proc can re-enter event dispatch. + # The window may close and set its buffer slot to nil while we're still here. + let stable = buffer + if stable == nil: return + if stable.buffers.len == 0: return + if stable.currentBuffer notin 0..stable.buffers.high: return + if stable.buffers.allIt(it.buffer.proxy.raw == nil): return + # first, lock, attach and commit current buffer - buffer.buffers[buffer.currentBuffer].locked = true + stable.buffers[stable.currentBuffer].locked = true # then, swap to not-locked buffer - for i, v in buffer.buffers: + for i, v in stable.buffers: if not v.locked: - buffer.currentBuffer = i + stable.currentBuffer = i break # if unlocked buffer was not found, wait up to timeout while trying again - if buffer.buffers[buffer.currentBuffer].locked: + if stable.buffers[stable.currentBuffer].locked: let deadline = now() + timeout block waiting_for_unlocked_buffer: while now() < deadline: - for i in 0..buffer.buffers.high: - if not buffer.buffers[i].locked: - buffer.currentBuffer = i + if stable.buffers.len == 0 or stable.buffers.allIt(it.buffer.proxy.raw == nil): + return + + for i in 0..stable.buffers.high: + if not stable.buffers[i].locked: + stable.currentBuffer = i break waiting_for_unlocked_buffer - discard wl_display_roundtrip buffer.globals.display # let libwayland process events + discard wl_display_roundtrip stable.globals.display # let libwayland process events - if buffer.buffers[buffer.currentBuffer].locked: + if stable.currentBuffer notin 0..stable.buffers.high: + return + if stable.buffers[stable.currentBuffer].locked: raise OsError.newException("timed out waiting for all buffers to be unlocked by server. (needed to commit shared buffer)") # current buffer are now unlocked and free to use. diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index e1aa54c..5d7e5c3 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -465,6 +465,9 @@ method pixelBuffer*(window: WindowWaylandSoftwareRendering): PixelBuffer = method swapBuffers(window: WindowWaylandSoftwareRendering) = + if window.m_closed: + return + if window.buffer == nil: if window.m_size.x <= 0 or window.m_size.y <= 0: return From 20de844d1f3c416a09d36c4517f1552a3e422197 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Wed, 18 Feb 2026 22:06:30 -0700 Subject: [PATCH 04/10] modifier keys --- src/siwin/platforms/wayland/window.nim | 45 +++++++++++++++++++++++--- src/siwin/platforms/x11/window.nim | 37 ++++++++++++++++++--- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index 5d7e5c3..9c983d9 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -184,6 +184,32 @@ proc waylandKeyToString(keycode: uint32): string = result.setLen 1 result.setLen global_xkb_state.xkb_state_key_get_utf8(keycode + 8, cast[cstring](result[0].addr), 7) +proc modifiersFromPressedKeys(keys: set[Key]): set[ModifierKey] = + if (keys * {Key.lshift, Key.rshift}).len != 0: + result.incl ModifierKey.shift + if (keys * {Key.lcontrol, Key.rcontrol}).len != 0: + result.incl ModifierKey.control + if (keys * {Key.lalt, Key.ralt, Key.level3_shift, Key.level5_shift}).len != 0: + result.incl ModifierKey.alt + if (keys * {Key.lsystem, Key.rsystem}).len != 0: + result.incl ModifierKey.system + if Key.capsLock in keys: + result.incl ModifierKey.capsLock + if Key.numLock in keys: + result.incl ModifierKey.numLock + +proc refreshKeyboardModifiers(window: WindowWayland) = + var modifiers = modifiersFromPressedKeys(window.keyboard.pressed) + if global_xkb_state != nil: + if global_xkb_state.xkb_state_mod_name_is_active("Lock".cstring, XKB_STATE_MODS_EFFECTIVE) != 0: + modifiers.incl ModifierKey.capsLock + if ( + global_xkb_state.xkb_state_mod_name_is_active("NumLock".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 or + global_xkb_state.xkb_state_mod_name_is_active("Mod2".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 + ): + modifiers.incl ModifierKey.numLock + window.keyboard.modifiers = modifiers + method swapBuffers(window: WindowWayland) {.base.} = discard @@ -515,7 +541,10 @@ proc releaseAllKeys(window: WindowWayland) = ## needed when window loses focus for k in window.keyboard.pressed.items.toSeq: window.keyboard.pressed.excl k - if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent(window: window, key: k, pressed: false, repeated: false, generated: true) + window.refreshKeyboardModifiers() + if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, key: k, pressed: false, repeated: false, generated: true, modifiers: window.keyboard.modifiers + ) method `minimized=`*(window: WindowWayland, v: bool) = @@ -758,8 +787,9 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = if siwinKey == Key.unknown: continue window.keyboard.pressed.incl siwinKey + window.refreshKeyboardModifiers() if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: siwinKey, pressed: true, generated: true + window: window, key: siwinKey, pressed: true, generated: true, modifiers: window.keyboard.modifiers ) window.lastPressedKey = siwinKey window.lastTextEntered = "" @@ -793,8 +823,9 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = else: window.keyboard.pressed.excl siwinKey + window.refreshKeyboardModifiers() if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: siwinKey, pressed: pressed + window: window, key: siwinKey, pressed: pressed, modifiers: window.keyboard.modifiers ) if pressed: @@ -820,6 +851,8 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = globals.seat_keyboard.onModifiers: globals.lastSeatEventSerial = serial discard global_xkb_state.xkb_state_update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group) + if globals.seat_keyboard_currentWindow != nil: + globals.seat_keyboard_currentWindow.WindowWayland.refreshKeyboardModifiers() globals.seat_keyboard.onRepeat_info: @@ -1280,12 +1313,14 @@ method step*(window: WindowWayland) = if window.lastPressedKey != Key.unknown and window.keyboard.pressed.contains(window.lastPressedKey): window.keyboard.pressed.excl window.lastPressedKey + window.refreshKeyboardModifiers() if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: window.lastPressedKey, pressed: false, repeated: true + window: window, key: window.lastPressedKey, pressed: false, repeated: true, modifiers: window.keyboard.modifiers ) window.keyboard.pressed.incl window.lastPressedKey + window.refreshKeyboardModifiers() if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: window.lastPressedKey, pressed: true, repeated: true + window: window, key: window.lastPressedKey, pressed: true, repeated: true, modifiers: window.keyboard.modifiers ) if window.lastTextEntered != "": diff --git a/src/siwin/platforms/x11/window.nim b/src/siwin/platforms/x11/window.nim index a7613d2..9c690bb 100644 --- a/src/siwin/platforms/x11/window.nim +++ b/src/siwin/platforms/x11/window.nim @@ -233,6 +233,23 @@ proc xkeyToKey(sym: KeySym): Key = of Xk_9: Key.n9 else: Key.unknown +proc modifiersFromPressedKeys(keys: set[Key]): set[ModifierKey] = + if (keys * {Key.lshift, Key.rshift}).len != 0: + result.incl ModifierKey.shift + if (keys * {Key.lcontrol, Key.rcontrol}).len != 0: + result.incl ModifierKey.control + if (keys * {Key.lalt, Key.ralt, Key.level3_shift, Key.level5_shift}).len != 0: + result.incl ModifierKey.alt + if (keys * {Key.lsystem, Key.rsystem}).len != 0: + result.incl ModifierKey.system + if Key.capsLock in keys: + result.incl ModifierKey.capsLock + if Key.numLock in keys: + result.incl ModifierKey.numLock + +proc refreshKeyboardModifiers(window: WindowX11) = + window.keyboard.modifiers = modifiersFromPressedKeys(window.keyboard.pressed) + proc newClientMessage[T](globals: SiwinGlobalsX11, window: x.Window, messageKind: Atom, data: openarray[T], serial: int = 0, sendEvent: bool = false): XEvent = result.theType = ClientMessage @@ -703,7 +720,10 @@ proc releaseAllKeys(window: WindowX11) = ## needed when window loses focus for k in window.keyboard.pressed.items: window.keyboard.pressed.excl k - window.eventsHandler.onKey.pushEvent KeyEvent(window: window, key: k, pressed: false, repeated: false, generated: true) + window.refreshKeyboardModifiers() + window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, key: k, pressed: false, repeated: false, generated: true, modifiers: window.keyboard.modifiers + ) for b in window.mouse.pressed: window.mouse.pressed.excl b @@ -1004,7 +1024,10 @@ method step*(window: WindowX11) = for k in keys: # press pressed in system keys if k == Key.unknown: continue window.keyboard.pressed.incl k - window.eventsHandler.onKey.pushEvent KeyEvent(window: window, pressed: false, repeated: false) + window.refreshKeyboardModifiers() + window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, key: k, pressed: false, repeated: false, modifiers: window.keyboard.modifiers + ) # todo: press pressed in system mouse buttons @@ -1217,7 +1240,10 @@ method step*(window: WindowX11) = var key = ev.xkey.extractKey if key != Key.unknown: window.keyboard.pressed.incl key - window.eventsHandler.onKey.pushEvent KeyEvent(window: window, key: key, pressed: true, repeated: repeated) + window.refreshKeyboardModifiers() + window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, key: key, pressed: true, repeated: repeated, modifiers: window.keyboard.modifiers + ) if window.eventsHandler.onTextInput != nil and window.xinContext != nil and (window.keyboard.pressed * {lcontrol, rcontrol, lalt, ralt}).len == 0: var status: Status @@ -1241,7 +1267,10 @@ method step*(window: WindowX11) = if repeated: prevEventIsKeyUpRepeated = true window.keyboard.pressed.excl key - window.eventsHandler.onKey.pushEvent KeyEvent(window: window, key: key, pressed: false, repeated: repeated) + window.refreshKeyboardModifiers() + window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, key: key, pressed: false, repeated: repeated, modifiers: window.keyboard.modifiers + ) of SelectionNotify: let clipboard = From 4339e602f2181c28953c3436b8f35c5728288e58 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Wed, 18 Feb 2026 22:13:22 -0700 Subject: [PATCH 05/10] modifier keys --- .../platforms/wayland/protocol_generated.nim | 3 +- src/siwin/platforms/wayland/window.nim | 20 +++++++++--- src/siwin/platforms/x11/window.nim | 31 ++++++++++++++++--- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/siwin/platforms/wayland/protocol_generated.nim b/src/siwin/platforms/wayland/protocol_generated.nim index 5a9edcb..c766114 100644 --- a/src/siwin/platforms/wayland/protocol_generated.nim +++ b/src/siwin/platforms/wayland/protocol_generated.nim @@ -4693,8 +4693,9 @@ proc get_keyboard*(this: Wl_seat): Wl_keyboard = ## never had the keyboard capability. The missing_capability error will ## be sent in this case. let interfaces = cast[ptr ptr WaylandInterfaces](this.proxy.raw.impl) + let version = min(9'u32, this.proxy.wl_proxy_get_version()) result = wl_proxy_marshal_flags(this.proxy.raw, 1, - addr(interfaces[].`iface Wl_keyboard`), 1, 0, + addr(interfaces[].`iface Wl_keyboard`), version, 0, nil).construct(interfaces[], Wl_keyboard, `Wl_keyboard / dispatch`, `Wl_keyboard / Callbacks`) diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index 9c983d9..49a8928 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -193,14 +193,24 @@ proc modifiersFromPressedKeys(keys: set[Key]): set[ModifierKey] = result.incl ModifierKey.alt if (keys * {Key.lsystem, Key.rsystem}).len != 0: result.incl ModifierKey.system - if Key.capsLock in keys: - result.incl ModifierKey.capsLock - if Key.numLock in keys: - result.incl ModifierKey.numLock proc refreshKeyboardModifiers(window: WindowWayland) = var modifiers = modifiersFromPressedKeys(window.keyboard.pressed) if global_xkb_state != nil: + if global_xkb_state.xkb_state_mod_name_is_active("Shift".cstring, XKB_STATE_MODS_EFFECTIVE) != 0: + modifiers.incl ModifierKey.shift + if global_xkb_state.xkb_state_mod_name_is_active("Control".cstring, XKB_STATE_MODS_EFFECTIVE) != 0: + modifiers.incl ModifierKey.control + if ( + global_xkb_state.xkb_state_mod_name_is_active("Mod1".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 or + global_xkb_state.xkb_state_mod_name_is_active("Alt".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 + ): + modifiers.incl ModifierKey.alt + if ( + global_xkb_state.xkb_state_mod_name_is_active("Mod4".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 or + global_xkb_state.xkb_state_mod_name_is_active("Super".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 + ): + modifiers.incl ModifierKey.system if global_xkb_state.xkb_state_mod_name_is_active("Lock".cstring, XKB_STATE_MODS_EFFECTIVE) != 0: modifiers.incl ModifierKey.capsLock if ( @@ -837,7 +847,7 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = window.lastPressedKey = Key.unknown var text = waylandKeyToString(key) - if Key.lcontrol in window.keyboard.pressed or Key.rcontrol in window.keyboard.pressed: text = "" + if ModifierKey.control in window.keyboard.modifiers: text = "" if text.len == 1 and text[0] < 32.char: text = "" if pressed and text != "": diff --git a/src/siwin/platforms/x11/window.nim b/src/siwin/platforms/x11/window.nim index 9c690bb..e42fcf4 100644 --- a/src/siwin/platforms/x11/window.nim +++ b/src/siwin/platforms/x11/window.nim @@ -242,13 +242,36 @@ proc modifiersFromPressedKeys(keys: set[Key]): set[ModifierKey] = result.incl ModifierKey.alt if (keys * {Key.lsystem, Key.rsystem}).len != 0: result.incl ModifierKey.system - if Key.capsLock in keys: + +proc modifiersFromStateMask(stateMask: cuint): set[ModifierKey] = + if (stateMask and ShiftMask.cuint) != 0: + result.incl ModifierKey.shift + if (stateMask and ControlMask.cuint) != 0: + result.incl ModifierKey.control + if (stateMask and Mod1Mask.cuint) != 0: + result.incl ModifierKey.alt + if (stateMask and Mod4Mask.cuint) != 0: + result.incl ModifierKey.system + if (stateMask and LockMask.cuint) != 0: result.incl ModifierKey.capsLock - if Key.numLock in keys: + if (stateMask and Mod2Mask.cuint) != 0: result.incl ModifierKey.numLock proc refreshKeyboardModifiers(window: WindowX11) = - window.keyboard.modifiers = modifiersFromPressedKeys(window.keyboard.pressed) + var rootWindow = window.globals.display.DefaultRootWindow + var childWindow: x.Window + var rootX, rootY: cint + var windowX, windowY: cint + var stateMask: cuint + let pointerKnown = window.globals.display.XQueryPointer( + rootWindow, rootWindow.addr, childWindow.addr, + rootX.addr, rootY.addr, windowX.addr, windowY.addr, stateMask.addr + ) != 0 + + var modifiers = modifiersFromPressedKeys(window.keyboard.pressed) + if pointerKnown: + modifiers = modifiers + modifiersFromStateMask(stateMask) + window.keyboard.modifiers = modifiers proc newClientMessage[T](globals: SiwinGlobalsX11, window: x.Window, messageKind: Atom, data: openarray[T], serial: int = 0, sendEvent: bool = false): XEvent = @@ -1245,7 +1268,7 @@ method step*(window: WindowX11) = window: window, key: key, pressed: true, repeated: repeated, modifiers: window.keyboard.modifiers ) - if window.eventsHandler.onTextInput != nil and window.xinContext != nil and (window.keyboard.pressed * {lcontrol, rcontrol, lalt, ralt}).len == 0: + if window.eventsHandler.onTextInput != nil and window.xinContext != nil and (window.keyboard.modifiers * {ModifierKey.control, ModifierKey.alt, ModifierKey.system}).len == 0: var status: Status var buffer: array[16, char] let length = window.xinContext.Xutf8LookupString(ev.xkey.addr, cast[cstring](buffer.addr), buffer.sizeof.cint, nil, status.addr) From 01c8a3e057ffae7c5bd2d169867c0b85cf405368 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Wed, 18 Feb 2026 22:20:10 -0700 Subject: [PATCH 06/10] modifier keys wayland --- src/siwin/platforms/wayland/siwinGlobals.nim | 1 + src/siwin/platforms/wayland/window.nim | 23 +++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/siwin/platforms/wayland/siwinGlobals.nim b/src/siwin/platforms/wayland/siwinGlobals.nim index 366ffc2..ffdca34 100644 --- a/src/siwin/platforms/wayland/siwinGlobals.nim +++ b/src/siwin/platforms/wayland/siwinGlobals.nim @@ -50,6 +50,7 @@ type # seat_touch_currentWindow*: Window seat_keyboard_repeatSettings*: tuple[rate, delay: int32] + seat_keyboard_repeatInfoReceived*: bool primaryClipboard*: Clipboard selectionClipboard*: Clipboard diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index 49a8928..d1c0ed2 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -781,6 +781,9 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = if `WlSeat / Capability`.keyboard in globals.seatCapabilities: globals.seat_keyboard = globals.seat.get_keyboard + globals.seat_keyboard_repeatInfoReceived = false + # Default repeat values; compositor-provided repeat_info overrides these. + globals.seat_keyboard_repeatSettings = (rate: 25, delay: 600) globals.seat_keyboard.onKeymap: updateKeymap(fd, size) @@ -793,6 +796,9 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = globals.lastSeatEventSerial = serial for key in keys.toSeq(uint32): + if global_xkb_state != nil: + discard global_xkb_state.xkb_state_update_key(key + 8, XKB_KEY_DIRECTION_DOWN) + let siwinKey = waylandKeyToKey(key) if siwinKey == Key.unknown: continue @@ -824,6 +830,11 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = if pressed: window.lastPressedKeyTime = getTime() globals.lastSeatEventSerial = serial + + if global_xkb_state != nil: + discard global_xkb_state.xkb_state_update_key( + key + 8, (if pressed: XKB_KEY_DIRECTION_DOWN else: XKB_KEY_DIRECTION_UP) + ) let siwinKey = waylandKeyToKey(key) @@ -833,11 +844,6 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = else: window.keyboard.pressed.excl siwinKey - window.refreshKeyboardModifiers() - if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: siwinKey, pressed: pressed, modifiers: window.keyboard.modifiers - ) - if pressed: if siwinKey notin Key.lcontrol..Key.rsystem: window.lastPressedKey = siwinKey @@ -845,6 +851,12 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = window.lastPressedKey = Key.unknown else: window.lastPressedKey = Key.unknown + + window.refreshKeyboardModifiers() + if siwinKey != Key.unknown and window.opened: + window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, key: siwinKey, pressed: pressed, modifiers: window.keyboard.modifiers + ) var text = waylandKeyToString(key) if ModifierKey.control in window.keyboard.modifiers: text = "" @@ -866,6 +878,7 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = globals.seat_keyboard.onRepeat_info: + globals.seat_keyboard_repeatInfoReceived = true globals.seat_keyboard_repeatSettings = (rate: rate, delay: delay) From fd611ae662283c6f24484dcf9732d25ca8a52ecc Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Wed, 18 Feb 2026 23:04:09 -0700 Subject: [PATCH 07/10] modifier keys wayland --- examples/text_input_demo.nim | 11 ++- src/siwin/platforms/wayland/window.nim | 98 +++++++++++++++++--------- 2 files changed, 75 insertions(+), 34 deletions(-) diff --git a/examples/text_input_demo.nim b/examples/text_input_demo.nim index 1d5cfd7..2689fc0 100644 --- a/examples/text_input_demo.nim +++ b/examples/text_input_demo.nim @@ -285,9 +285,18 @@ proc present(state: var TextInputDemoState, window: Window) = copyMem(pixelBuffer.data, state.image.data[0].addr, bytes) convertPixelsInplace(pixelBuffer.data, pixelBuffer.size, PixelBufferFormat.rgbx_32bit, pixelBuffer.format) +proc demoPreferredPlatform(): Platform = + let forced = getEnv("SIWIN_PLATFORM").strip().toLowerAscii() + case forced + of "wayland": Platform.wayland + of "x11": Platform.x11 + else: defaultPreferedPlatform() + proc main() = + let platform = demoPreferredPlatform() + echo "text_input_demo platform=", platform let globals = newSiwinGlobals( - preferedPlatform = (when defined(linux): x11 else: defaultPreferedPlatform()) + preferedPlatform = platform ) let window = globals.newSoftwareRenderingWindow( size = ivec2(960, 540), diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index d1c0ed2..0860123 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -47,9 +47,12 @@ type kind: WindowWaylandKind ## Is this a normal window or is it a layer shell surface? lastPressedKey: Key + lastPressedRawKeycode: uint32 + lastPressedRawKeyDown: bool lastTextEntered: string lastPressedKeyTime: Time lastKeyRepeatedTime: Time + lastRepeatTraceAt: Time ClipboardWayland* = ref object of Clipboard globals: SiwinGlobalsWayland @@ -184,6 +187,13 @@ proc waylandKeyToString(keycode: uint32): string = result.setLen 1 result.setLen global_xkb_state.xkb_state_key_get_utf8(keycode + 8, cast[cstring](result[0].addr), 7) +proc repeatTraceEnabled(): bool = + getEnv("SIWIN_WAYLAND_REPEAT_TRACE").len != 0 + +proc repeatTrace(msg: string) = + if repeatTraceEnabled(): + stderr.writeLine("[siwin-wayland-repeat] " & msg) + proc modifiersFromPressedKeys(keys: set[Key]): set[ModifierKey] = if (keys * {Key.lshift, Key.rshift}).len != 0: result.incl ModifierKey.shift @@ -555,6 +565,9 @@ proc releaseAllKeys(window: WindowWayland) = if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( window: window, key: k, pressed: false, repeated: false, generated: true, modifiers: window.keyboard.modifiers ) + window.lastPressedKey = Key.unknown + window.lastPressedRawKeyDown = false + window.lastTextEntered = "" method `minimized=`*(window: WindowWayland, v: bool) = @@ -825,10 +838,15 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = if globals.seat_keyboard_currentWindow == nil: return let window = globals.seat_keyboard_currentWindow.WindowWayland - let pressed = state == `WlKeyboard / Key_state`.pressed + let pressed = state != `WlKeyboard / Key_state`.released if pressed: + window.lastPressedRawKeycode = key + window.lastPressedRawKeyDown = true window.lastPressedKeyTime = getTime() + window.lastTextEntered = "" + elif key == window.lastPressedRawKeycode: + window.lastPressedRawKeyDown = false globals.lastSeatEventSerial = serial if global_xkb_state != nil: @@ -853,6 +871,7 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = window.lastPressedKey = Key.unknown window.refreshKeyboardModifiers() + repeatTrace(&"key state={(if pressed: \"down\" else: \"up\")} stateRaw={cast[uint32](state)} raw={key} mapped={siwinKey} rawDown={window.lastPressedRawKeyDown} rate={window.globals.seat_keyboard_repeatSettings.rate} delay={window.globals.seat_keyboard_repeatSettings.delay} mods={window.keyboard.modifiers}") if siwinKey != Key.unknown and window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( window: window, key: siwinKey, pressed: pressed, modifiers: window.keyboard.modifiers @@ -879,7 +898,9 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = globals.seat_keyboard.onRepeat_info: globals.seat_keyboard_repeatInfoReceived = true - globals.seat_keyboard_repeatSettings = (rate: rate, delay: delay) + if rate > 0 and delay >= 0: + globals.seat_keyboard_repeatSettings = (rate: rate, delay: delay) + repeatTrace(&"repeat_info rate={rate} delay={delay} appliedRate={globals.seat_keyboard_repeatSettings.rate} appliedDelay={globals.seat_keyboard_repeatSettings.delay}") proc setIdleInhibit*(window: WindowWayland, state: bool) = @@ -1318,38 +1339,49 @@ method step*(window: WindowWayland) = if eventCount <= 2: # seems like idle event count is 2 sleep(1) - if window.globals.seat_keyboard_currentWindow == window: - # repeat keys if needed - if ( - (window.globals.seat_keyboard_repeatSettings.rate > 0 and window.globals.seat_keyboard_repeatSettings.rate < 1000) and - (window.keyboard.pressed - {Key.lcontrol, Key.lshift, Key.lalt, Key.rcontrol, Key.rshift, Key.ralt}).len != 0 - ): - let repeatStartTime = window.lastPressedKeyTime + initDuration(milliseconds = window.globals.seat_keyboard_repeatSettings.delay) - let nows = getTime() - let interval = initDuration(milliseconds = 1000 div window.globals.seat_keyboard_repeatSettings.rate) - - if repeatStartTime <= nows and window.lastKeyRepeatedTime < repeatStartTime - interval: - window.lastKeyRepeatedTime = repeatStartTime - interval - - while repeatStartTime <= nows and window.lastKeyRepeatedTime + interval <= nows: - window.lastKeyRepeatedTime += interval - - if window.lastPressedKey != Key.unknown and window.keyboard.pressed.contains(window.lastPressedKey): - window.keyboard.pressed.excl window.lastPressedKey - window.refreshKeyboardModifiers() - if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: window.lastPressedKey, pressed: false, repeated: true, modifiers: window.keyboard.modifiers - ) - window.keyboard.pressed.incl window.lastPressedKey - window.refreshKeyboardModifiers() - if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: window.lastPressedKey, pressed: true, repeated: true, modifiers: window.keyboard.modifiers - ) + # repeat keys if needed + if ( + window.globals.seat_keyboard_repeatSettings.rate > 0 and + window.lastPressedRawKeyDown + ): + let repeatStartTime = window.lastPressedKeyTime + initDuration(milliseconds = window.globals.seat_keyboard_repeatSettings.delay) + let nows = getTime() + let interval = initDuration(milliseconds = max(1'i64, (1000 div window.globals.seat_keyboard_repeatSettings.rate).int64)) + if repeatTraceEnabled() and (window.lastRepeatTraceAt + initDuration(milliseconds = 300) <= nows): + window.lastRepeatTraceAt = nows + repeatTrace(&"tick rawDown=true raw={window.lastPressedRawKeycode} rate={window.globals.seat_keyboard_repeatSettings.rate} delay={window.globals.seat_keyboard_repeatSettings.delay} sincePressMs={(nows - window.lastPressedKeyTime).inMilliseconds} startReady={(repeatStartTime <= nows)}") + + if repeatStartTime <= nows and window.lastKeyRepeatedTime < repeatStartTime - interval: + window.lastKeyRepeatedTime = repeatStartTime - interval + + while repeatStartTime <= nows and window.lastKeyRepeatedTime + interval <= nows: + window.lastKeyRepeatedTime += interval + + let repeatedKey = waylandKeyToKey(window.lastPressedRawKeycode) + var repeatedText = waylandKeyToString(window.lastPressedRawKeycode) + if ModifierKey.control in window.keyboard.modifiers: + repeatedText = "" + if repeatedText.len == 1 and repeatedText[0] < 32.char: + repeatedText = "" + + if repeatedKey != Key.unknown and window.keyboard.pressed.contains(repeatedKey): + window.keyboard.pressed.excl repeatedKey + window.refreshKeyboardModifiers() + if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, key: repeatedKey, pressed: false, repeated: true, modifiers: window.keyboard.modifiers + ) + window.keyboard.pressed.incl repeatedKey + window.refreshKeyboardModifiers() + if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, key: repeatedKey, pressed: true, repeated: true, modifiers: window.keyboard.modifiers + ) - if window.lastTextEntered != "": - if window.opened: window.eventsHandler.onTextInput.pushEvent TextInputEvent( - window: window, text: window.lastTextEntered, repeated: true - ) + if repeatedText != "": + window.lastTextEntered = repeatedText + if window.opened: window.eventsHandler.onTextInput.pushEvent TextInputEvent( + window: window, text: repeatedText, repeated: true + ) + repeatTrace(&"emit raw={window.lastPressedRawKeycode} mapped={repeatedKey} textLen={repeatedText.len} repeatedAtMs={(window.lastKeyRepeatedTime - window.lastPressedKeyTime).inMilliseconds}") let nows = getTime() if window.opened: window.eventsHandler.onTick.pushEvent TickEvent(window: window, deltaTime: nows - window.lastTickTime) From 859c9cc46c308f2c78502108b9653047d42f0e20 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Thu, 19 Feb 2026 00:15:27 -0700 Subject: [PATCH 08/10] cleanup and fix wayland repeat --- examples/text_input_demo.nim | 11 +---------- src/siwin/platforms/wayland/siwinGlobals.nim | 1 - src/siwin/platforms/wayland/window.nim | 16 ---------------- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/examples/text_input_demo.nim b/examples/text_input_demo.nim index 2689fc0..1d5cfd7 100644 --- a/examples/text_input_demo.nim +++ b/examples/text_input_demo.nim @@ -285,18 +285,9 @@ proc present(state: var TextInputDemoState, window: Window) = copyMem(pixelBuffer.data, state.image.data[0].addr, bytes) convertPixelsInplace(pixelBuffer.data, pixelBuffer.size, PixelBufferFormat.rgbx_32bit, pixelBuffer.format) -proc demoPreferredPlatform(): Platform = - let forced = getEnv("SIWIN_PLATFORM").strip().toLowerAscii() - case forced - of "wayland": Platform.wayland - of "x11": Platform.x11 - else: defaultPreferedPlatform() - proc main() = - let platform = demoPreferredPlatform() - echo "text_input_demo platform=", platform let globals = newSiwinGlobals( - preferedPlatform = platform + preferedPlatform = (when defined(linux): x11 else: defaultPreferedPlatform()) ) let window = globals.newSoftwareRenderingWindow( size = ivec2(960, 540), diff --git a/src/siwin/platforms/wayland/siwinGlobals.nim b/src/siwin/platforms/wayland/siwinGlobals.nim index ffdca34..366ffc2 100644 --- a/src/siwin/platforms/wayland/siwinGlobals.nim +++ b/src/siwin/platforms/wayland/siwinGlobals.nim @@ -50,7 +50,6 @@ type # seat_touch_currentWindow*: Window seat_keyboard_repeatSettings*: tuple[rate, delay: int32] - seat_keyboard_repeatInfoReceived*: bool primaryClipboard*: Clipboard selectionClipboard*: Clipboard diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index 0860123..f01afc7 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -52,7 +52,6 @@ type lastTextEntered: string lastPressedKeyTime: Time lastKeyRepeatedTime: Time - lastRepeatTraceAt: Time ClipboardWayland* = ref object of Clipboard globals: SiwinGlobalsWayland @@ -187,13 +186,6 @@ proc waylandKeyToString(keycode: uint32): string = result.setLen 1 result.setLen global_xkb_state.xkb_state_key_get_utf8(keycode + 8, cast[cstring](result[0].addr), 7) -proc repeatTraceEnabled(): bool = - getEnv("SIWIN_WAYLAND_REPEAT_TRACE").len != 0 - -proc repeatTrace(msg: string) = - if repeatTraceEnabled(): - stderr.writeLine("[siwin-wayland-repeat] " & msg) - proc modifiersFromPressedKeys(keys: set[Key]): set[ModifierKey] = if (keys * {Key.lshift, Key.rshift}).len != 0: result.incl ModifierKey.shift @@ -794,7 +786,6 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = if `WlSeat / Capability`.keyboard in globals.seatCapabilities: globals.seat_keyboard = globals.seat.get_keyboard - globals.seat_keyboard_repeatInfoReceived = false # Default repeat values; compositor-provided repeat_info overrides these. globals.seat_keyboard_repeatSettings = (rate: 25, delay: 600) @@ -871,7 +862,6 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = window.lastPressedKey = Key.unknown window.refreshKeyboardModifiers() - repeatTrace(&"key state={(if pressed: \"down\" else: \"up\")} stateRaw={cast[uint32](state)} raw={key} mapped={siwinKey} rawDown={window.lastPressedRawKeyDown} rate={window.globals.seat_keyboard_repeatSettings.rate} delay={window.globals.seat_keyboard_repeatSettings.delay} mods={window.keyboard.modifiers}") if siwinKey != Key.unknown and window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( window: window, key: siwinKey, pressed: pressed, modifiers: window.keyboard.modifiers @@ -897,10 +887,8 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = globals.seat_keyboard.onRepeat_info: - globals.seat_keyboard_repeatInfoReceived = true if rate > 0 and delay >= 0: globals.seat_keyboard_repeatSettings = (rate: rate, delay: delay) - repeatTrace(&"repeat_info rate={rate} delay={delay} appliedRate={globals.seat_keyboard_repeatSettings.rate} appliedDelay={globals.seat_keyboard_repeatSettings.delay}") proc setIdleInhibit*(window: WindowWayland, state: bool) = @@ -1347,9 +1335,6 @@ method step*(window: WindowWayland) = let repeatStartTime = window.lastPressedKeyTime + initDuration(milliseconds = window.globals.seat_keyboard_repeatSettings.delay) let nows = getTime() let interval = initDuration(milliseconds = max(1'i64, (1000 div window.globals.seat_keyboard_repeatSettings.rate).int64)) - if repeatTraceEnabled() and (window.lastRepeatTraceAt + initDuration(milliseconds = 300) <= nows): - window.lastRepeatTraceAt = nows - repeatTrace(&"tick rawDown=true raw={window.lastPressedRawKeycode} rate={window.globals.seat_keyboard_repeatSettings.rate} delay={window.globals.seat_keyboard_repeatSettings.delay} sincePressMs={(nows - window.lastPressedKeyTime).inMilliseconds} startReady={(repeatStartTime <= nows)}") if repeatStartTime <= nows and window.lastKeyRepeatedTime < repeatStartTime - interval: window.lastKeyRepeatedTime = repeatStartTime - interval @@ -1381,7 +1366,6 @@ method step*(window: WindowWayland) = if window.opened: window.eventsHandler.onTextInput.pushEvent TextInputEvent( window: window, text: repeatedText, repeated: true ) - repeatTrace(&"emit raw={window.lastPressedRawKeycode} mapped={repeatedKey} textLen={repeatedText.len} repeatedAtMs={(window.lastKeyRepeatedTime - window.lastPressedKeyTime).inMilliseconds}") let nows = getTime() if window.opened: window.eventsHandler.onTick.pushEvent TickEvent(window: window, deltaTime: nows - window.lastTickTime) From 83eb94c18a53309851ef8b052b4413f2a8e23af4 Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Thu, 19 Feb 2026 00:18:54 -0700 Subject: [PATCH 09/10] skip t_opengl* on macos --- siwin.nimble | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/siwin.nimble b/siwin.nimble index 4892b76..3619d8b 100644 --- a/siwin.nimble +++ b/siwin.nimble @@ -118,9 +118,18 @@ task installAndroidDeps, "install android dependencies": const testTargets = ["t_opengl_es", "t_opengl", "t_swrendering", "t_multiwindow", "t_vulkan", "t_offscreen"] +proc shouldSkipTarget(target, args: string): bool = + let targetingMacos = + (when defined(macosx): true else: false) or + args.contains("--os:macosx") + targetingMacos and target in ["t_opengl", "t_opengl_es"] + proc runTests(args: string, envPrefix = "") = withDir "tests": for target in testTargets: + if shouldSkipTarget(target, args): + echo "Skipping ", target, " on macOS" + continue exec (if envPrefix.len != 0: envPrefix & " " else: "") & "nim c " & args & " --hints:off -r " & target proc runTestsForSession(args: string) = @@ -166,10 +175,14 @@ task testMacos, "test macos": createZigccIfNeeded() let pwd = getCurrentDir() let target = "x86_64-macos-none" + let args = &"--os:macosx --cc:clang --clang.exe:{pwd}/build/zigcc --clang.linkerexe:{pwd}/build/zigcc --passc:--target={target} --passl:--target={target} --hints:off" withDir "tests": for file in testTargets: + if shouldSkipTarget(file, args): + echo "Skipping ", file, " on macOS" + continue try: - exec &"nim c --os:macosx --cc:clang --clang.exe:{pwd}/build/zigcc --clang.linkerexe:{pwd}/build/zigcc --passc:--target={target} --passl:--target={target} --hints:off -o:{file}-macos {file}" + exec &"nim c {args} -o:{file}-macos {file}" exec &"echo ./{file}-macos | darling shell" except: discard From 5b3587cfc0819805052411668530d32981f51f1b Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Thu, 19 Feb 2026 00:25:09 -0700 Subject: [PATCH 10/10] update todos --- TODO.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TODO.md b/TODO.md index 45cb4b4..02cad45 100644 --- a/TODO.md +++ b/TODO.md @@ -7,6 +7,8 @@ - [ ] X11: define and implement DPI-scaling policy for mouse move/click coordinates (for example using `Xft.dpi`) so coordinates are consistent with the physical-pixel model. ## Wayland +- [x] Make `KeyEvent.modifiers` reflect effective xkb modifier state (including remaps like Caps-as-Ctrl), not only raw pressed key symbols. +- [x] Fix keyboard repeat handling in `text_input_demo`/Wayland path (robust hold/repeat behavior with sane fallback repeat settings). - [ ] Use current xkb state for key mapping so `KeyEvent` respects active layout/group, not only unmodified symbols. - [ ] Improve scroll handling by consuming `axis_source`, `axis_discrete`, and `axis_value120` events instead of relying on a fixed divisor. - [ ] Revisit scroll normalization to avoid hardcoded `kde_default_mousewheel_scroll_length = 15`. @@ -14,10 +16,14 @@ - [ ] Expose IME preedit/composition updates (composition string, cursor/candidate position) to app callbacks. ## X11 +- [x] Make `KeyEvent.modifiers` include live X11 modifier-mask state so remapped modifiers (for example Caps-as-Ctrl) are reflected correctly. - [ ] Improve wheel handling beyond fixed button 4/5/6/7 `-1/+1` deltas. - [ ] Investigate support for user scroll preferences (direction/speed) where available. - [ ] Improve XIM text-input path to handle multi-stage IME composition updates more explicitly. +## Testing +- [x] Skip `tests/t_opengl.nim` and `tests/t_opengl_es.nim` when targeting macOS in Nimble test runners. + ## Cocoa (macOS) - [x] Implement missing core window methods on Cocoa: `close`, `size=`, `pos=`, `fullscreen=`, `maximized=`, `minimized=`, `resizable=`, `minSize=`, `maxSize=`, `vsync=`, `icon=`. - [x] Fix selector typo for `otherMouseUp:` so extra mouse button release events are dispatched correctly.