Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@
- [ ] 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`.
- [ ] Add Wayland text-input protocol support (`zwp_text_input_v3`) for robust IME behavior.
- [ ] 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.
Expand Down
1 change: 1 addition & 0 deletions examples/text_input_demo.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 14 additions & 1 deletion siwin.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion src/siwin/platforms/wayland/protocol_generated.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

Expand Down
107 changes: 89 additions & 18 deletions src/siwin/platforms/wayland/sharedBuffer.nim
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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 =
Expand All @@ -36,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
Expand Down Expand Up @@ -67,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.
Expand Down Expand Up @@ -122,17 +141,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.cstring,
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()


Expand Down
Loading