changeset 1457:ac6ebf679d63

fix: unicode text input handling on linux
author sam <sam@basx.dev>
date Sat, 22 Mar 2025 14:24:07 +0700
parents af7754b983d3
children 5e4b0b771dc4
files semicongine/platform/linux/rendering.nim
diffstat 1 files changed, 57 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/platform/linux/rendering.nim	Sat Mar 22 01:01:40 2025 +0700
+++ b/semicongine/platform/linux/rendering.nim	Sat Mar 22 14:24:07 2025 +0700
@@ -123,7 +123,9 @@
     quit "Failed to open display"
   discard XSetErrorHandler(XErrorLogger)
 
-  let rootWindow = display.XDefaultRootWindow()
+  let screen = display.XDefaultScreen()
+  let rootWindow = display.XRootWindow(screen)
+  let vis = display.XDefaultVisual(screen)
   discard display.XkbSetDetectableAutoRepeat(true, nil)
   var
     attribs: XWindowAttributes
@@ -131,34 +133,39 @@
     height = cuint(600)
   checkXlibResult display.XGetWindowAttributes(rootWindow, addr(attribs))
 
-  var attrs = XSetWindowAttributes()
-  let window = XCreateWindow(
-    display,
+  var attrs = XSetWindowAttributes(
+    event_mask:
+      FocusChangeMask or KeyPressMask or KeyReleaseMask or ExposureMask or
+      VisibilityChangeMask or StructureNotifyMask or ButtonMotionMask or ButtonPressMask or
+      ButtonReleaseMask
+  )
+  let window = display.XCreateWindow(
     rootWindow,
     (attribs.width - cint(width)) div 2,
     (attribs.height - cint(height)) div 2,
     width,
     height,
     0,
-    CopyFromParent,
+    display.XDefaultDepth(screen),
     InputOutput,
-    cast[PVisual](CopyFromParent),
-    0, # CWOverrideRedirect,
-    addr attrs, # foregroundColor, backgroundColor
+    vis,
+    CWEventMask,
+    addr(attrs),
   )
-  checkXlibResult XSetStandardProperties(
-    display, window, title, "window", 0, nil, 0, nil
+  checkXlibResult display.XSetStandardProperties(
+    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)
+  # get an input context, to allow encoding of key-events to characters
 
-  deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false))
-  checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1)
+  let im = XOpenIM(display, nil, nil, nil)
+  assert im != nil
+  let ic = im.XCreateIC(XNInputStyle, XIMPreeditNothing or XIMStatusNothing, nil)
+  assert ic != nil
+
+  checkXlibResult display.XMapWindow(window)
+
+  deleteMessage = display.XInternAtom("WM_DELETE_WINDOW", XBool(false))
+  checkXlibResult display.XSetWMProtocols(window, addr(deleteMessage), 1)
 
   var data = "\0".cstring
   var pixmap = display.XCreateBitmapFromData(window, data, 1, 1)
@@ -167,11 +174,13 @@
     display.XCreatePixmapCursor(pixmap, pixmap, addr(color), addr(color), 0, 0)
   checkXlibResult display.XFreePixmap(pixmap)
 
-  # get an input context, to allow encoding of key-events to characters
-  let im = XOpenIM(display, nil, nil, nil)
-  let ic = XCreateIC(
-    im, XNInputStyle, XIMPreeditNothing or XIMStatusNothing, XNClientWindow, window, nil
-  )
+  discard display.XSync(0)
+
+  # wait until window is shown
+  var ev: XEvent
+  while ev.theType != MapNotify:
+    discard display.XNextEvent(addr(ev))
+
   return
     NativeWindow(display: display, window: window, emptyCursor: empty_cursor, ic: ic)
 
@@ -223,38 +232,44 @@
   vec2i(attribs.width, attribs.height)
 
 # buffer to save utf8-data from keyboard events
-var unicodeData = newString(16)
+var unicodeData: array[64, char]
 
 proc pendingEvents*(window: NativeWindow): seq[Event] =
   var event: XEvent
+
   while window.display.XPending() > 0:
     discard window.display.XNextEvent(addr(event))
+
+    if XFilterEvent(addr(event), None) != 0:
+      continue
+
     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)
-      var e =
-        Event(eventType: KeyPressed, key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN))
-      let len = Xutf8LookupString(
-        window.ic, keyevent, unicodeData, unicodeData.len.cint, nil, nil
+      var e = Event(
+        eventType: KeyPressed,
+        key: KeyTypeMap.getOrDefault(int(event.xkey.keycode), Key.UNKNOWN),
       )
-      if len > 0:
-        unicodeData.setLen(len)
+      var status: Status
+      var ksym: KeySym = NoSymbol
+      let len = window.ic.Xutf8LookupString(
+        addr(event.xkey), addr(unicodeData[0]), unicodeData.len.cint, nil, addr(status)
+      )
+      if len > 0 and status != XBufferOverflow:
+        unicodeData[len] = '\0'
         for r in unicodeData.runes():
           e.char = r
           break
       result.add e
     of KeyRelease:
-      let keyevent = cast[PXKeyEvent](addr(event))
-      let xkey = int(keyevent.keycode)
       result.add Event(
-        eventType: KeyReleased, key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN)
+        eventType: KeyReleased,
+        key: KeyTypeMap.getOrDefault(int(event.xkey.keycode), Key.UNKNOWN),
       )
     of ButtonPress:
-      let button = int(cast[PXButtonEvent](addr(event)).button)
+      let button = int(event.xbutton.button)
       if button == Button4:
         result.add Event(eventType: MouseWheel, amount: 1'f32)
       elif button == Button5:
@@ -265,20 +280,24 @@
           button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN),
         )
     of ButtonRelease:
-      let button = int(cast[PXButtonEvent](addr(event)).button)
+      let button = int(event.xbutton.button)
       result.add Event(
         eventType: MouseReleased,
         button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN),
       )
     of FocusIn:
+      window.ic.XSetICFocus()
       result.add Event(eventType: GotFocus)
     of FocusOut:
+      window.ic.XUnsetICFocus()
       result.add Event(eventType: LostFocus)
     of ConfigureNotify, Expose:
       result.add Event(eventType: ResizedWindow)
     else:
       discard
 
+  discard window.display.XFlush()
+
 proc getMousePosition*(window: NativeWindow): Vec2i =
   var
     root: x11.Window