Skip to content

Commit

Permalink
Merge pull request #27 from levovix0/master
Browse files Browse the repository at this point in the history
x11: clipboard; wayland: unmarshaling, protocol
  • Loading branch information
guzba authored Oct 27, 2021
2 parents 085e6dc + 2819cc8 commit bf6a1d2
Show file tree
Hide file tree
Showing 6 changed files with 829 additions and 86 deletions.
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

0 comments on commit bf6a1d2

Please sign in to comment.