Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

develop linux #40

Merged
merged 17 commits into from
Dec 30, 2021
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
2 changes: 1 addition & 1 deletion examples/fullscreen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let bxy = newBoxy()
proc display() =
bxy.beginFrame(window.size)
bxy.drawRect(rect(vec2(0, 0), window.size.vec2), color(1, 1, 1, 1))
bxy.drawRect(rect(vec2(100, 100), vec2(200, 200)), color(1, 0, 1, 1))
bxy.drawRect(rect((window.size.vec2 / 2) - vec2(100, 100), vec2(200, 200)), color(1, 0, 1, 1))
bxy.endFrame()
window.swapBuffers()

Expand Down
97 changes: 85 additions & 12 deletions src/windy/platforms/linux/wayland.nim
Original file line number Diff line number Diff line change
@@ -1,16 +1,89 @@
import wayland/protocol
import vmath
import ../../common, ../../internal
import wayland/[protocol, sharedBuffer, egl]

let display = connect()
display.onError:
echo "Error for ", objId.uint32, ": ", code, ", ", message
var
initialized: bool

let reg = display.registry
var compositor: Compositor
display: Display
registry: Registry

reg.onGlobal:
echo (id: name.uint32, iface: iface, version: version)
case iface
of "wl_compositor":
compositor = reg.bindInterface(Compositor, name, iface, version)
compositor: Compositor
shm: Shm
shell: XdgWmBase

sync display
pixelFormats: seq[PixelFormat]


proc init* =
if initialized: return

display = connect()
display.onError:
raise WindyError.newException("Wayland error for " & $objId.uint32 & ": " & $code & ", " & message)

registry = display.registry

registry.onGlobal:
case iface
of Compositor.iface:
compositor = registry.bindInterface(Compositor, name, iface, version)

of Shm.iface:
shm = registry.bindInterface(Shm, name, iface, version)

shm.onFormat:
pixelFormats.add format

of XdgWmBase.iface:
shell = registry.bindInterface(XdgWmBase, name, iface, version)

shell.onPing:
shell.pong(serial)

sync display

if compositor == nil or shm == nil or shell == nil:
raise WindyError.newException(
"Not enough Wayland interfaces, missing: " &
(if compositor == nil: "wl_compositor " else: "") &
(if shm == nil: "wl_shm " else: "") &
(if shell == nil: "xdg_wm_base " else: "")
)

sync display

initEgl()

initialized = true

when isMainModule:
init()
let srf = compositor.newSurface
let ssrf = shell.shellSurface(srf)
let tl = ssrf.toplevel

commit srf

ssrf.onConfigure:
ssrf.ackConfigure(serial)
commit srf

tl.onClose: quit()

sync display

let buf = shm.create(ivec2(128, 128), PixelFormat.xrgb8888)
attach srf, buf.buffer, ivec2(0, 0)
commit srf

makeCurrent newOpenglContext()

# how to draw on window?
# i tried:
# creating context on window (incompatible native window (wl_window vs. protocol.Window))
# eglCreateDRMImageMESA/eglExportDRMImageMESA/wl_drm.newBuffer (fails via BadAlloc)
# in this code works:
# setting pixels manually on buf.dataAddr (no OpenGL)

while true: sync display
138 changes: 87 additions & 51 deletions src/windy/platforms/linux/wayland/basic.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import os, posix, nativesockets, net, tables
## note: this file is included in protocol.nim, don't import it directly

import os, posix, nativesockets, net, tables, sequtils
import ../../../common

type
Expand All @@ -15,10 +17,6 @@ type

socket: Socket
ids: Table[uint32, Proxy]
lastId: Id

Callback* = ref object of Proxy
done: proc(cbData: uint32)


proc `//>`[T: SomeInteger](a, b: T): T =
Expand All @@ -44,19 +42,55 @@ proc asString[T](x: openarray[T]): string =
cast[string](x.asSeq(char))


proc connect*(name = getEnv("WAYLAND_SOCKET")): Display =
new result, (proc(d: Display) = close d.socket)

result.display = result
result.id = Id 1
result.ids[1] = result

let d = result
result.deleteId = proc(id: Id) =
d.ids.del id.uint32

var name =
if name != "": $name
else: "wayland-0"

if not name.isAbsolute:
var runtimeDir = getEnv("XDG_RUNTIME_DIR")
if runtimeDir == "": raise WindyError.newException("XDG_RUNTIME_DIR not set in the environment")
name = runtimeDir / name

let sock = createNativeSocket(posix.AF_UNIX, posix.SOCK_STREAM or posix.SOCK_CLOEXEC, 0)
if sock == osInvalidSocket: raise WindyError.newException("Failed to create socket")

var a = "\1\0" & name

if sock.connect(cast[ptr SockAddr](a[0].addr), uint32 a.len) < 0:
close sock
raise WindyError.newException("Failed to connect to wayland server")

result.socket = newSocket(sock, nativesockets.AF_UNIX, nativesockets.SOCK_STREAM, nativesockets.IPPROTO_IP)


proc new(d: Display, t: type): t =
inc d.lastId
proc findHole: uint32 =
for k in 2 ..< (2 + d.ids.len.uint32):
if not d.ids.hasKey k: return k

let id = findHole()
new result
result.display = d
result.id = d.lastId
d.ids[d.lastId.uint32] = result
result.id = Id id
d.ids[id] = result

proc destroy*(x: Proxy) =
x.display.ids.del x.id.uint32


proc serialize[T](x: T): seq[uint32] =
when x is uint32|int32|Id|enum|float32|FileDescriptor:
when x is uint32|int32|Id|enum|float32:
result.add cast[uint32](x)

elif x is int:
Expand Down Expand Up @@ -87,20 +121,34 @@ proc serialize[T](x: T): seq[uint32] =
result.add x.serialize

elif x is set:
when T.sizeof > uint.sizeof: {.error: "too large set".}
when T.sizeof > uint32.sizeof: {.error: "too large set".}
result.add cast[uint32](x)

elif x is Proxy:
result.add x.id.uint32

elif x is FileDescriptor: discard # will be stored in the ancillary data of the UNIX domain socket message (msg_control)

elif T.sizeof == uint32.sizeof:
result.add cast[uint32](x)

else: {.error: "unserializable type " & $T.}


proc fileDescriptors[T](x: T): seq[FileDescriptor] =
when x is FileDescriptor: result.add x

elif x is seq|array:
for x in x:
result.add fileDescriptors(x)

elif x is tuple|object:
for x in x.fields:
result.add fileDescriptors(x)


proc deserialize(display: Display, x: seq[uint32], T: type, i: var uint32): T =
when result is uint32|int32|Id|enum|float32|FileDescriptor:
when result is uint32|int32|Id|enum|float32:
result = cast[T](x[i]); i += 1

elif result is int:
Expand Down Expand Up @@ -136,6 +184,9 @@ proc deserialize(display: Display, x: seq[uint32], T: type, i: var uint32): T =
when T.sizeof > uint.sizeof: {.error: "too large set".}
result = cast[T](x[i]); i += 1

elif result is FileDescriptor:
## todo

elif T.sizeof == uint32.sizeof:
result = cast[T](x[i]); i += 1

Expand All @@ -154,7 +205,31 @@ proc marshal[T](x: Proxy, op: int, data: T = ()) =
var d = data.serialize
d.insert ((d.len.uint32 * uint32.sizeof.uint32 + 8) shl 16) or (op.uint32 and 0x0000ffff)
d.insert x.id.uint32
assert x.display.socket.send(d[0].addr, d.len * uint32.sizeof) == d.len * uint32.sizeof

let fds = data.fileDescriptors

var iovec = IOVec(
iov_base: d[0].addr,
iov_len: csize_t d.len * uint32.sizeof,
)

var hdr = 0.cint.repeat(csize_t.sizeof div cint.sizeof) & @[SOL_SOCKET, SCM_RIGHTS] & cast[seq[cint]](fds)
cast[ptr csize_t](hdr[0].addr)[] = csize_t hdr.len * cint.sizeof

var msg = Tmsghdr(
msg_iov: iovec.addr,
msg_iovlen: 1,
msg_control:
if fds.len == 0: nil
else: hdr[0].addr,
msg_controllen:
if fds.len == 0: 0.csize_t
else: csize_t hdr.len * cint.sizeof
)

let len = x.display.socket.getFd.sendmsg(msg.addr, 0x4000)
assert len == d.len * uint32.sizeof


method unmarshal(x: Proxy, op: int, data: seq[uint32]) {.base, locks: "unknown".} = discard

Expand All @@ -170,42 +245,3 @@ proc pollNextEvent(d: Display) =

if not d.ids.hasKey id: return # event for destroyed object
d.ids[id].unmarshal(op.int, data)


proc connect*(name = getEnv("WAYLAND_SOCKET")): Display =
new result, (proc(d: Display) = close d.socket)

result.display = result
result.id = Id 1
result.lastId = Id 1
result.ids[1] = result

let d = result
result.deleteId = proc(id: Id) =
if id.uint32 == 2: return # re-use Callback reserved for syncing
d.ids.del id.uint32

var name =
if name != "": $name
else: "wayland-0"

if not name.isAbsolute:
var runtimeDir = getEnv("XDG_RUNTIME_DIR")
if runtimeDir == "": raise WindyError.newException("XDG_RUNTIME_DIR not set in the environment")
name = runtimeDir / name

let sock = createNativeSocket(posix.AF_UNIX, posix.SOCK_STREAM or posix.SOCK_CLOEXEC, 0)
if sock == osInvalidSocket: raise WindyError.newException("Failed to create socket")

var a = "\1\0" & name

if sock.connect(cast[ptr SockAddr](a[0].addr), uint32 name.len + 2) < 0:
close sock
raise WindyError.newException("Failed to connect to wayland server")

result.socket = newSocket(sock, nativesockets.AF_UNIX, nativesockets.SOCK_STREAM, nativesockets.IPPROTO_IP)

# reserve Callback for syncing
discard result.new(Callback)
result.marshal(0, Id 2)
result.pollNextEvent
Loading