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

x11: clipboard; wayland: unmarshaling, protocol #27

Merged
merged 12 commits into from
Oct 27, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# normal ignores:
*.exe
*.out
nimcache
*.pdb
*.ilk
Expand Down
16 changes: 16 additions & 0 deletions src/windy/platforms/linux/wayland.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import wayland/protocol

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

let reg = display.registry
var compositor: Compositor

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

sync display
230 changes: 160 additions & 70 deletions src/windy/platforms/linux/wayland/basic.nim
Original file line number Diff line number Diff line change
@@ -1,67 +1,189 @@
import os, posix, nativesockets, asyncnet, asyncdispatch
import os, posix, nativesockets, net, tables
import ../../../common

type
Id* = distinct uint32
FileDescriptor* = distinct int32

Proxy* = ref object of RootObj
display: Display
version: int
id: Id

Display* = ref object of Proxy
socket: AsyncSocket
lastId: Id
error*: proc(objId: Id, code: int, message: string)
deleteId*: proc(id: Id)

Registry* = ref object of Proxy
socket: Socket
ids: Table[uint32, Proxy]
lastId: Id

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

Id = distinct uint32

proc `//>`[T: SomeInteger](a, b: T): T =
## div roundup
(a + b - 1) div b

proc asSeq[A](x: A, B: type = uint8): seq[B] =
if x.len == 0: return
result = newSeq[B]((A.sizeof + B.sizeof - 1) div B.sizeof)
result = newSeq[B](A.sizeof //> B.sizeof)
copyMem(result[0].addr, x.unsafeaddr, A.sizeof)

proc asSeq(s: string, T: type = uint8): seq[T] =
proc asSeq(s: string, T: type): seq[T] =
if s.len == 0: return
result = newSeq[T]((s.len + T.sizeof - 1) div T.sizeof)
result = newSeq[T](s.len //> T.sizeof)
copyMem(result[0].addr, s[0].unsafeaddr, s.len)

proc asSeq[A](x: seq[A], B: type = uint8): seq[B] =
proc asSeq[A](x: openarray[A], B: type): seq[B] =
if x.len == 0: return
result = newSeq[B]((x.len * A.sizeof + B.sizeof - 1) div B.sizeof)
result = newSeq[B]((x.len * A.sizeof) //> B.sizeof)
copyMem(result[0].addr, x[0].unsafeaddr, x.len * A.sizeof)

proc asString[T](x: openarray[T]): string =
cast[string](x.asSeq(char))


proc openLocalSocket: SocketHandle =
const
localDomain = posix.AF_UNIX
stream = posix.SOCK_STREAM
closeex = posix.SOCK_CLOEXEC
einval = 22
proc new(d: Display, t: type): t =
inc d.lastId
new result
result.display = d
result.id = d.lastId
d.ids[d.lastId.uint32] = result

result = createNativeSocket(localDomain, stream or closeex, 0)
if result != osInvalidSocket: return
proc destroy*(x: Proxy) =
x.display.ids.del x.id.uint32

var errno {.importc.}: cint
if errno == einval: raise WindyError.newException("Failed to create socket")

result = createNativeSocket(localDomain, stream, 0)
if result == osInvalidSocket: raise WindyError.newException("Failed to create socket")
proc serialize[T](x: T): seq[uint32] =
when x is uint32|int32|Id|enum|float32|FileDescriptor:
result.add cast[uint32](x)

let flags = fcntl(result.cint, F_GETFD)
if flags == -1:
close result
raise WindyError.newException("Failed to create socket")
elif x is int:
result.add cast[uint32](x.int32)

if fcntl(result.cint, F_SETFD, flags or FD_CLOEXEC) == -1:
close result
raise WindyError.newException("Failed to create socket")
elif x is float:
result.add cast[uint32](x.float32)

elif x is bool:
result.add x.uint32

elif x is string:
let x = x & '\0'
result.add x.len.uint32
result.add x.asSeq(uint32)

elif x is seq:
type T = typeof(x[0])
result.add (x.len * T.sizeof).uint32
result.add x.asSeq(uint32)

elif x is tuple|object:
for x in x.fields:
result.add x.serialize

elif x is array:
for x in x:
result.add x.serialize

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

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

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

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


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

elif result is int:
result = cast[int32](x[i]).int; i += 1

elif result is float:
result = cast[float32](x[i]).float; i += 1

elif result is bool:
result = x[i].bool; i += 1

elif result is string:
let len = x[i]; i += 1
let lenAligned = len //> uint32.sizeof.uint32
result = x[i ..< i + lenAligned].asString; i += lenAligned
result.setLen len - 1

elif result is seq:
type T = typeof(result[0])
let len = x[i]; i += 1
let lenAligned = (len * T.sizeof.uint32) //> uint32.sizeof.uint32
result = x[i ..< i + lenAligned].asSeq(T); i += lenAligned

elif result is tuple|object:
for v in result.fields:
v = deserialize(display, x, typeof(v), i)

elif result is array:
for v in result.mitems:
v = deserialize(display, x, typeof(v), i)

elif result is set:
when T.sizeof > uint.sizeof: {.error: "too large set".}
result = cast[T](x[i]); i += 1

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

elif result is Proxy:
result.display = display
result.id = x[i].Id; i += 1

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

proc deserialize(display: Display, x: seq[uint32], T: type): T =
var i: uint32
deserialize(display, x, T, i)


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

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


proc pollNextEvent(d: Display) =
let head = d.socket.recv(2 * uint32.sizeof).asSeq(uint32)
let id = head[0]
let op = head[1] and 0xffff
let len = int (head[1] shr 16)
assert len >= 8

let data = d.socket.recv(len - 8).asSeq(uint32)

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
Expand All @@ -72,50 +194,18 @@ proc connect*(name = getEnv("WAYLAND_SOCKET")): Display =
if runtimeDir == "": raise WindyError.newException("XDG_RUNTIME_DIR not set in the environment")
name = runtimeDir / name

let sock = openLocalSocket()
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")

register sock.AsyncFD
result.socket = newAsyncSocket(sock.AsyncFD, nativesockets.AF_UNIX, nativesockets.SOCK_STREAM, nativesockets.IPPROTO_IP)


template sock: AsyncSocket = this.display.socket

proc newId(d: Display): Id =
inc d.lastId
d.lastId


proc serialize[T](x: T): seq[uint32] =
when x is uint32|int32|Id:
result.add cast[uint32](x)
elif x is string:
let x = x & '\0'
result.add x.len
result.add x.asSeq(uint32)
elif x is seq:
type T = typeof(x[0])
result.add x.len * T.sizeof
result.add x.asSeq(uint32)
elif x is tuple|object:
for x in x.fields:
result.add x.serialize
elif x is ref|ptr:
{.error: "cannot serialize non-value object".}
else:
result.add x.asSeq(uint32)


proc marshal[T](this: Proxy, op: int, data: T) {.async.} =
var d = data.serialize
d = @[this.id.uint32, (d.len.uint32 shl 16) or (op.uint32 and 0x0000ffff)] & data.serialize
await sock.send(d[0].addr, d.len * uint32.sizeof)

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

proc registry*(d: Display): Future[Registry] {.async.} =
result = Registry(display: d, id: d.newId)
await d.marshal(1, result.id)
# reserve Callback for syncing
discard result.new(Callback)
result.marshal(0, Id 2)
result.pollNextEvent
Loading