From 26a6b751b2397d279f63407fa34dfe33bf3ba9a3 Mon Sep 17 00:00:00 2001 From: Stefan Salewski Date: Wed, 2 Dec 2020 15:58:41 +0100 Subject: [PATCH] fixes and example for libnice --- examples/gtk3/simple_example.nim | 310 +++++++++++++++++++++++++++++++ gintro/gimplglib.nim | 67 +++++++ gintro/gimplgtk.nim | 6 +- gintro/gimplnice.nim | 105 +++++++++++ tests/gen.nim | 237 ++++++++++++++++++++--- 5 files changed, 700 insertions(+), 25 deletions(-) create mode 100644 examples/gtk3/simple_example.nim create mode 100644 gintro/gimplglib.nim create mode 100644 gintro/gimplnice.nim diff --git a/examples/gtk3/simple_example.nim b/examples/gtk3/simple_example.nim new file mode 100644 index 0000000..e1c3330 --- /dev/null +++ b/examples/gtk3/simple_example.nim @@ -0,0 +1,310 @@ +## https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/examples/simple-example.c +## nim c --gc:arc simple_example.nim +## Example using libnice to negotiate a UDP connection between two clients, +## possibly on the same network or behind different NATs and/or stateful +## firewalls. +## +## Run two clients, one controlling and one controlled: +## simple-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }') +## simple-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }') +## + +# https://forum.nim-lang.org/t/3752 +when (compiles do: import gintro/gtk): + import gintro/[gtk, glib, gobject, gio, nice] +else: + import gintro/[dummygtk, glib, gobject, gio, nice] # For windows with glib but no gtk + +from strutils import `%`, split +from os import paramCount, paramStr +from strutils import parseInt + +from posix import INET6_ADDRSTRLEN + +when defined(windows): + import winlean +else: + discard # import posix + +var + gloop: glib.MainLoop + ioStdin: glib.IOChannel + streamId: int + candidateTypeName = ["host", "srflx", "prflx", "relay"] + stateName = ["disconnected", "gathering", "connecting", "connected", "ready", "failed"] + +proc gMsg(s: string) = + stdout.write(s) + flushFile(stdout) + +proc gDebug(s: string) = + stdout.write(s) + flushFile(stdout) + +proc gError(s: string) = + stderr.write(s) + flushFile(stderr) + +proc toStringVal(s: string): Value = + let gtype = gStringGetType() + discard init(result, gtype) + setString(result, s) + +proc toIntVal(i: int): Value = + let gtype = typeFromName("gint") + discard init(result, gtype) + setInt(result, i) + +proc printLocalData(agent: nice.Agent; streamId: int; componentId: int): int = + var + localUfrag: string + localPassword: string + ipaddr = newString(INET6_ADDRSTRLEN).cstring # not really nice as that cstring is used as out parameter by toString() + cands: seq[Candidate] + result = QuitFailure + block gotoEnd: + if not getLocalCredentials(agent, streamId, localUfrag, localPassword): + break gotoEnd + cands = getLocalCandidates(agent, streamId, componentId) + if cands.len == 0: + break gotoEnd + stdout.write(localUfrag, localPassword) + for el in cands: + toString(el.impl.`addr`, ipaddr) + ## (foundation),(prio),(addr),(port),(type) + stdout.write(' ', cast[cstring](addr el.impl.foundation), ',', el.impl.priority, ',', ipaddr, ' ', getPort(el.impl.`addr`), + ',', candidateTypeName[el.impl.`type`.ord]) + echo "" + # end label + result = QuitSuccess + return result + +proc parseCandidate(scand: string; streamId: int): nice.Candidate = + var + cand: nice.Candidate = nil + ntype: nice.CandidateType = CandidateType.host # that initialization is never used! + tokens = scand.split(',', 5) + block gotoEnd: + for i in 0 .. 4: + if tokens[i] == "": + break gotoEnd + #[ should work too + for i, el in candidateTypeName: + var h = false + if tokens[4] == el: + ntype = i + h = true + if not h: + break gotoEnd + ]# + var i: int + while i < candidateTypeName.len: + if tokens[4] == candidateTypeName[i]: + ntype = CandidateType(i) + inc(i) + if i == candidateTypeName.len: + break gotoEnd + cand = newCandidate(ntype) + #cand.impl.componentId = 1 + cand.setComponentID(1) # we have a few manually created getters and setters already + cand.impl.streamId = uint32(streamId) + cand.impl.transport = nice.CandidateTransport.udp + cand.setFoundation(tokens[0]) + cand.impl.priority = uint32(parseInt(tokens[1])) + if not setFromString(cand.impl.`addr`, tokens[2]): + gMsg("failed to parse addr: " & tokens[2]) + cand = nil + break gotoEnd + setPort(cand.impl.`addr`, parseInt(tokens[3])) + # end label + return cand + +proc parseRemoteData(agent: Agent; streamId: int; componentId: int; line: string): cint = + var + remoteCandidates: seq[Candidate] + lineArgv: seq[string] + ufrag: string + passwd: string + result = QuitFailure + lineArgv = split(line, {' ', '\t', '\n'}) + block gotoEnd: + for i, x in lineArgv: + if x.len == 0: + continue + ## first two args are remote ufrag and password + if ufrag == "": + ufrag = x + elif passwd == "": + passwd = x + else: + ## Remaining args are serialized canidates (at least one is required) + var c: nice.Candidate = parseCandidate(lineArgv[i], streamId) + if c == nil: + gMsg("failed to parse candidate: " & lineArgv[i]) + break gotoEnd + remoteCandidates.add(c) # caution prepend in C code! + if ufrag == "" or passwd == "" or remoteCandidates.len == 0: + gMsg("line must have at least ufrag, password, and one candidate") + break gotoEnd + if not setRemoteCredentials(agent, streamId, ufrag, passwd): + gMsg("failed to set remote credentials") + break gotoEnd + ## Note: this will trigger the start of negotiation. + if setRemoteCandidates(agent, streamId, componentId, remoteCandidates) < 1: + gMsg("failed to set remote candidates") + break gotoEnd + # end label + result = QuitSuccess + return result + +proc stdinRemoteInfoCb(source: glib.IOChannel; cond: glib.IOCondition; agent: nice.Agent): bool = + var + line: string + rval: cint + ret: bool = true + if readLine(source, line) == IOStatus.normal: + ## Parse remote candidate list and set it on the agent + rval = parseRemoteData(agent, streamId, 1, line) + if rval == QuitSuccess: + ## Return FALSE so we stop listening to stdin since we parsed the + ## candidates correctly + ret = false + gDebug("waiting for state READY or FAILED signal...") + else: + write(stderr, "ERROR: failed to parse remote data\n") + echo("Enter remote data (single line, no wrapping):") + stdout.write("> ") + flushFile(stdout) + return ret + +proc cbCandidateGatheringDone(agent: nice.Agent; streamId: int) = + gDebug("SIGNAL candidate gathering done\n") + ## Candidate gathering is done. Send our local candidates on stdout + echo("Copy this line to remote client:") + echo(" ") + discard printLocalData(agent, streamId, 1) + echo("") + ## Listen on stdin for the remote candidate list + echo("Enter remote data (single line, no wrapping):") + addWatch(ioStdin, glib.PRIORITY_DEFAULT, IOCondition.in , stdinRemoteInfoCb, agent) + stdout.write("> ") + flushFile(stdout) + +proc cbNewSelectedPair(agent: nice.Agent; streamId: int; componentId: int; lfoundation: string; rfoundation: string) = + gDebug("SIGNAL: selected pair " & lfoundation & " " & rfoundation) + +proc stdinSendDataCb(source: IOChannel; cond: IOCondition; agent: nice.Agent): bool = + var line: string + if readLine(source, line) == IOStatus.normal: + discard send(agent, streamId, 1, line.len, line) + stdout.write("> ") + flushFile(stdout) + else: + discard send(agent, streamId, 1, 1, "\x00") + ## Ctrl-D was pressed. + quit(gloop) + return true + +proc cbComponentStateChanged(agent: nice.Agent; streamId: int; componentId: int; state: int) = + gDebug("SIGNAL: state changed $1 $2 $3[$4]\n" % [$streamId, $componentId, stateName[state], $state]) + if state == ComponentState.connected.ord: + var + local: Candidate + remote: nice.Candidate + ## Get current selected candidate pair and print IP address used + if getSelectedPair(agent, streamId, componentId, local, remote): + var ipaddr = newString(INET6_ADDRSTRLEN).cstring + toString(local.impl.`addr`, ipaddr) + echo("\nNegotiation complete: ([$1]:$2,", [$ipaddr, $getPort(local.impl.`addr`)]) + toString(remote.impl.`addr`, ipaddr) + echo(" [$1]:$2)" % [$ipaddr, $getPort(remote.impl.`addr`)]) + ## Listen to stdin and send data written to it + echo("\nSend lines to remote (Ctrl-D to quit):") + addWatch(ioStdin, glib.PRIORITY_DEFAULT, IOCondition.in , stdinSendDataCb, agent) + stdout.write("> ") + flushFile(stdout) + elif state == ComponentState.failed.ord: + quit(gloop) + +proc cbNiceRecv(agent: nice.Agent; streamID: cuint; componentID: cuint; len: cuint; buf: cstring) = #{.cdecl.} = + if len == 1 and buf[0] == '\x00': + glib.quit(gloop) + discard stdout.writeBuffer(buf, len) + flushfile(stdout) + +proc main = + var + agent: nice.Agent + stunAddr: string + stunPort: int + controlling: int + + ## Parse arguments + let argc = paramCount() + 1 + var argv = newSeq[string](argc) + for i in 0 ..< argc: + argv[i] = paramStr(i) + + if argc > 4 or argc < 2 or argv[1].len > 1: + write(stderr, "Usage: $1 0|1 stun_addr [stun_port]\n" % [argv[0]]) + quit(QuitFailure) + + controlling = argv[1][0].ord - '0'.ord + if controlling != 0 and controlling != 1: + write(stderr, "Usage: $1 0|1 stun_addr [stun_port]\n" % [argv[0]]) + quit(QuitFailure) + + if argc > 2: + stunAddr = argv[2] + if argc > 3: + stunPort = parseInt(argv[3]) + else: + stunPort = 3478 + gDebug("Using stun server \'[$1]:$2\'\n" % [$stunAddr, $stunPort]) + + gio.networkingInit() + gloop = newMainLoop(nil, false) + + # https://gist.github.com/demotomohiro/c9d9ad7e17639c6acb9bd2be7c204f3f + when hostOS == "windows": + ioStdin = win32NewFd(system.stdin.getFileHandle) + else: + ioStdin = unixNew(system.stdin.getFileHandle) + + ## Create the nice agent + agent = nice.newAgent(glib.getContext(gloop), nice.CompatibilityRfc5245) + if agent == nil: + gError("Failed to create agent") + + ## Set the STUN settings and controlling mode + if stunAddr.len > 0: + setProperty(agent, "stun-server", toStringVal(stunAddr)) + setProperty(agent, "stun-server-port", toIntVal(stunPort)) + setProperty(agent, "controlling-mode", toIntVal(controlling)) + + ## Connect to the signals + agent.connect("candidate-gathering-done", cbCandidateGatheringDone) + agent.connect("new-selected-pair", cbNewSelectedPair) + agent.connect("component-state-changed", cbComponentStateChanged) + + ## Create a new stream with one component + streamId = addStream(agent, 1) + if streamId == 0: + gError("Failed to add stream") + + ## Attach to the component to receive the data + ## Without this call, candidates cannot be gathered + attachRecv(agent, streamId.cuint, 1, getContext(gloop), cbNiceRecv) + + ## Start gathering local candidates + if not gatherCandidates(agent, streamId): + gError("Failed to start candidate gathering") + gDebug("waiting for candidate-gathering-done signal...") + + ## Run the mainloop. Everything else will happen asynchronously + ## when the candidates are done gathering. + run(gloop) + quit(QuitSuccess) + +main() + diff --git a/gintro/gimplglib.nim b/gintro/gimplglib.nim new file mode 100644 index 0000000..408ce91 --- /dev/null +++ b/gintro/gimplglib.nim @@ -0,0 +1,67 @@ +import macros +from strutils import `%` + +macro mAddWatch(ch: IOChannel; pri: cint; cond: IOCondition; p: untyped; arg: typed; ignoreArg: bool): untyped = + var IdleID {.compileTime, global.}: int + inc(IdleID) + let ats = $getTypeInst(arg).toStrLit + let procName = "giofunc_" & $IdleID + let procNameCdecl = "giofunc_cdecl_" & $IdleID + var r1s = """ + +proc $1(self: ptr IOChannel00; cond: IOCondition; userData: pointer): gboolean {.cdecl.} = + var ch1 {.global.}: glib.IOChannel + if ch1.isNil: + new ch1 + GC_ref(ch1) # never call destroy/finalizer on this global variable + ch1.ignoreFinalizer = true + ch1.impl = self + discard $2(ch1, cond""" + + if not ignoreArg.boolVal: + if getTypeInst(arg).typeKind == ntyRef: + r1s.add(", cast[$3](userdata)") + else: + r1s.add(", cast[$3](userdata)") + r1s.add(")\n") + + r1s = r1s % [$procNameCdecl, $p, ats] + + let r2s = + if ignoreArg.boolVal: + + """ +proc $1(self: IOChannel; pri: cint; cond: IOCondition) = + g_io_add_watch_full(cast[ptr IOChannel00](self.impl), pri, cond, $5, nil, nil) + +$1($5, $3, $4) +""" % [$procName, ats, $pri, $cond, $procNameCdecl, $(arg.toStrLit), $(ch.toStrLit)] + + else: + + """ +proc $1(self: IOChannel; pri: cint; cond: IOCondition; a: $2) = + when (a is ref object): + GC_ref(a) + discard g_io_add_watch_full(cast[ptr IOChannel00](self.impl), pri, cond, $5, cast[pointer](a), nil) + else: + var ar: ref $2 + new(ar) + #deepCopy(ar[], a) + ar[] = a + GC_ref(ar) + discard g_io_add_watch_full(cast[ptr IOChannel00](self.impl), pri, cond, $5, cast[pointer](ar[]), nil) +$1($7, $3, $8.$4, $6) +""" % [$procName, ats, $(pri.toStrLit), $(cond.toStrLit), $procNameCdecl, $(arg.toStrLit), $(ch.toStrLit), $(getTypeInst( + cond).toStrLit)] + #echo r1s + #echo r2s + result = parseStmt(r1s & r2s) + +template addWatch*(ch: IOChannel; pri: int; cond: IOCondition; p: untyped; arg: typed): untyped = + maddWatch(ch, pri.cint, cond, p, arg, false) + +template addWatch*(ch: IOChannel; pri: int; cond: IOCondition; p: untyped): untyped = + maddWatch(ch, pri.cint, cond, p, "", true) + + diff --git a/gintro/gimplgtk.nim b/gintro/gimplgtk.nim index 398d2ee..0fd1af3 100644 --- a/gintro/gimplgtk.nim +++ b/gintro/gimplgtk.nim @@ -172,7 +172,7 @@ $1($5) proc $1(self: DrawingArea; a: $2) = when (a is ref object): GC_ref(a) - gtk_drawing_area_set_draw_func(self.impl, $3, cast[pointer](a), nil) + gtk_drawing_area_set_draw_func(cast[ptr DrawingArea00](self.impl), $3, cast[pointer](a), nil) else: var ar: ref $2 new(ar) @@ -183,8 +183,8 @@ proc $1(self: DrawingArea; a: $2) = $1($5, $4) """ % [$procName, ats, $procNameCdecl, $(arg.toStrLit), $(da.toStrLit)] - echo r1s - echo r2s + #echo r1s + #echo r2s result = parseStmt(r1s & r2s) template setDrawFunc*(da: DrawingArea; p: untyped; arg: typed): untyped = diff --git a/gintro/gimplnice.nim b/gintro/gimplnice.nim new file mode 100644 index 0000000..5cda7b8 --- /dev/null +++ b/gintro/gimplnice.nim @@ -0,0 +1,105 @@ +proc nice_agent_attach_recv*(self: ptr Object00; streamID: cuint; componentID: cuint; ctx: ptr glib.MainContext00; + fn: AgentRecvFunc; data: pointer): gboolean {.importc, libprag.} + +# we will never use this proc, as we use the macro! +proc attachRecv*(self: Agent; streamID: int; componentID: int; ctx: MainContext; fn: AgentRecvFunc; data: pointer): bool = + toBool(nice_agent_attach_recv(self.impl, streamID.cuint, componentID.cuint, ctx.impl, fn, data)) + +import macros + +macro mattachRecv(ag: Agent; streamID: cuint; componentID: cuint; ctx: glib.MainContext; p: untyped; arg: typed; + ignoreArg: bool): untyped = + var IdleID {.compileTime, global.}: int + inc(IdleID) + let ats = $getTypeInst(arg).toStrLit + let procName = "recvfunc_" & $IdleID + let procNameCdecl = "recvfunc_cdecl_" & $IdleID + var r1s = """ + +proc $1(self: ptr Agent00; streamId: uint32; componentId: uint32; len: uint32; buf: cstring; userData: pointer) {.cdecl.} = + var ag1 {.global.}: nice.Agent + if ag1.isNil: + new ag1 + GC_ref(ag1) # never call destroy/finalizer on this global variable + ag1.ignoreFinalizer = true + ag1.impl = self + $2(ag1, streamID, componentID, len, buf""" + + if not ignoreArg.boolVal: + if getTypeInst(arg).typeKind == ntyRef: + r1s.add(", cast[$3](userdata)") + else: + r1s.add(", cast[$3](userdata)") + r1s.add(")\n") + + r1s = r1s % [$procNameCdecl, $p, ats] + + let r2s = + if ignoreArg.boolVal: + + """ +proc $1(self: Agent; streamId: uint32; componentId: uint32; ctx: glib.MainContext) = + #gtk_drawing_area_set_draw_func(self.impl, $3, nil, nil) + #g_io_add_watch_full(cast[ptr IOChannel00](self.impl), pri, cond, $5, nil, nil) + discard nice_agent_attach_recv(cast[ptr Agent00](self.impl), streamId, componentId, cast[ptr MainContext00](ctx.impl), $7, nil)#, userData) + #gtk_drawing_area_set_draw_func(cast[ptr DrawingArea00](self.impl), $3, nil, nil) + +$1($5, $3, $4, $6) +""" % [$procName, ats, $(streamID.toStrLit), $(componentID.toStrLit), $(ag.toStrLit), $(ctx.toStrLit), $procNameCdecl, $(arg.toStrLit)] + + else: + + """ +proc $1(self: Agent; streamId: uint32; componentId: uint32; a: $2) = + when (a is ref object): + GC_ref(a) + discard nice_agent_attach_recv(cast[ptr Agent00](self.impl), streamId, componentId, $7, cast[pointer](a)) + #discard g_io_add_watch_full(cast[ptr IOChannel00](self.impl), pri, cond, $5, cast[pointer](a), nil) + else: + var ar: ref $2 + new(ar) + #deepCopy(ar[], a) + ar[] = a + GC_ref(ar) + discard nice_agent_attach_recv(cast[ptr Agent00](self.impl), streamId, componentId, $7, cast[pointer](a[])) + #discard g_io_add_watch_full(cast[ptr IOChannel00](self.impl), pri, cond, $5, cast[pointer](ar[]), nil) +$1($5, $3, $4, $5, $8) +""" % [$procName, ats, $streamID, $componentID, $(ag.toStrLit), "xx", $procNameCdecl, $(arg.toStrLit)] + #echo r1s + #echo r2s + result = parseStmt(r1s & r2s) + +template attachRecv*(ag: Agent; streamID: cuint; componentID: cuint; ctx: glib.MainContext; p: untyped; arg: typed): untyped = + mattachRecv(ag, streamID, componentID, ctx, p, arg, false) + +template attachRecv*(ag: Agent; streamID: cuint; componentID: cuint; ctx: glib.MainContext; p: untyped): untyped = + mattachRecv(ag, streamID, componentID, ctx, p, "", true) + +proc setAddress*(c: Candidate; s: cstring): bool = + toBool(nice_address_set_from_string(c.impl.addr, s)) + +proc setPort*(c: Candidate; p: uint32) = + nice_address_set_port(c.impl.addr, p) + +proc setTransport*(c: Candidate; t: CandidateTransport) = + c.impl.transport = t + +proc setcomponentID*(c: Candidate; id: uint32) = + c.impl.componentId = id + +proc setStreamID*(c: Candidate; id: uint32) = + c.impl.streamId = id + +proc setPriority*(c: Candidate; pri: uint32) = + c.impl.priority = pri + +proc setFoundation*(c: Candidate; s: cstring) = + var i: int + while i < c.impl.foundation.high: + c.impl.foundation[i] = s[i].int8 + if s[i] == '\x0': + break + inc(i) + c.impl.foundation[^1] = 0 + + diff --git a/tests/gen.nim b/tests/gen.nim index f28e6ba..3045d2b 100644 --- a/tests/gen.nim +++ b/tests/gen.nim @@ -1,6 +1,6 @@ # High level gobject-introspection based GTK3/GTK4 bindings for the Nim programming language # nimpretty --maxLineLen:130 gen.nim -# v 0.8.4 2020-NOV-18 +# v 0.8.4 2020-DEC-02 # (c) S. Salewski 2018 # usefull for finding death code: @@ -440,7 +440,7 @@ gobject.EnumClass gtk.RecentData glib.SList glib.List -nice.Candidate +nice.Address """ const autoCallerAlloc = """ @@ -612,6 +612,7 @@ type cstrA intA0 glist + gslist namedAX intAX @@ -674,6 +675,23 @@ proc newGenRec(t: GITypeInfo; genProxy = false): RecRes = result.namePlainNS = result[0] result.name00 = result[0] return + + if tag == GITypeTag.GSLIST: + let arrayType = gTypeInfoGetParamType(t, 0) + let atag = gTypeInfoGetTag(arrayType) # INTERFACE or UTF8 + if atag == GITypeTag.INTERFACE: + let aface = gTypeInfoGetInterface(arrayType) + result[3] = gBaseInfoGetType(aface) + var child = newGenRec(arrayType, genProxy = true).namePlain # [0] + child = mangleType(mangleName(child)) + result[0] = "ptr glib.SList" + result[1] = gslist + result[2] = child + result.namePlain = result[0] + result.namePlainNS = result[0] + result.name00 = result[0] + return + if tag == GITypeTag.ARRAY: result.flags.incl(RecResFlag.array) let toa = gTypeInfoGetArrayType(t) # type of array @@ -921,7 +939,6 @@ proc genPars(info: GICallableInfo; genProxy = false; binfo: GIBaseInfo = nil; ge str = ngr.namePlain else: str = ngr.name00 - #var istr = str if genProxy and ngr.res == glist: result.tempRes = " var tempResGL = seq2GList($1)\n" % [mangleName(gBaseInfoGetName(arg))] if gArgInfoGetOwnershipTransfer(arg) == GITransfer.NOTHING: @@ -930,6 +947,16 @@ proc genPars(info: GICallableInfo; genProxy = false; binfo: GIBaseInfo = nil; ge str = "var seq[$1]" % [ngr.childname] else: str = "seq[$1]" % [ngr.childname] + + if genProxy and ngr.res == gslist: + result.tempRes = " var tempResGL = seq2GSList($1)\n" % [mangleName(gBaseInfoGetName(arg))] + if gArgInfoGetOwnershipTransfer(arg) == GITransfer.NOTHING: + result.tempResFree = " g_slist_free(tempResGL)\n" + if str.startsWith("var "): + str = "var seq[$1]" % [ngr.childname] + else: + str = "seq[$1]" % [ngr.childname] + if (ngr.flags * {RecResFlag.unamedA, namedA} != {}) and genProxy and not gTypeInfoIsZeroTerminated(t): assert ngr.flags.contains(RecResFlag.array) assert not (ngr.flags.contains(RecResFlag.zeroTerminated)) @@ -980,12 +1007,16 @@ proc genPars(info: GICallableInfo; genProxy = false; binfo: GIBaseInfo = nil; ge else: if ngr.res == glist: result.arglist.add("tempResGL") + elif ngr.res == gslist: + result.arglist.add("tempResGL") + elif ngr.flags.contains(RecResFlag.unamedA) and (gArgInfoGetDirection(arg) == GIDirection.IN or gArgInfoIsCallerAllocates(arg)): assert ngr.flags.contains(RecResFlag.array) result.arglist.add("cast[" & ngr.name00 & "](unsafeaddr($1[0]))" % name) elif (ngr.flags * {RecResFlag.namedA, cstringA} != {}) and gArgInfoGetDirection(arg) == GIDirection.IN: assert ngr.flags.contains(RecResFlag.array) - result.arglist.add("seq2$1" % [str.replace("00").capitalizeAscii.fixedName] & '(' & name & ", " & StringArrayName & "$1)" % [num]) + result.arglist.add("seq2$1" % [str.replace("00").capitalizeAscii.fixedName] & '(' & name & ", " & StringArrayName & + "$1)" % [num]) num = "2" elif needTypeConv(str, ngr) and gArgInfoGetDirection(arg) in {GIDirection.OUT, GIDirection.INOUT}: result.arglist.add(name.strip(chars = {'`'}) & "_00") @@ -1103,6 +1134,9 @@ proc genPars(info: GICallableInfo; genProxy = false; binfo: GIBaseInfo = nil; ge r = ngrRet.name00 if genProxy and ngrRet.res == glist: r = "seq[$1]" % [ngrRet.childname] + if genProxy and ngrRet.res == gslist: + r = "seq[$1]" % [ngrRet.childname] + var hhh = ngrRet.namePlainNS if hhh.startsWith("gtk4", "gdk4"): hhh.delete(3, 3) @@ -1154,7 +1188,6 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo) = assert(gCallableInfoGetCallerOwns(minfo) in {GITransfer.NOTHING, EVERYTHING}) # both occur methodBuffer.write(pars.tempRes) methodBuffer.writeLine(" let gobj = " & sym & pars.arglist) - methodBuffer.write(pars.tempResFree) checkForGerror() if gCallableInfoMayReturnNull(minfo): methodBuffer.writeLine(" if gobj.isNil:") @@ -1373,7 +1406,7 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo) = if sym in ["g_quark_from_static_string", "g_error_free", "g_object_get_qdata", "g_object_ref_sink", "g_object_unref", "g_timeout_add_full", "g_object_is_floating", "g_type_from_name", "g_type_check_instance_is_a", "g_idle_add_full", "g_quark_try_string", "vte_regex_unref", - "gtk_builder_get_object", "g_action_map_add_action", "gtk_drawing_area_set_draw_func"]: + "gtk_builder_get_object", "g_action_map_add_action", "gtk_drawing_area_set_draw_func", "g_io_add_watch_full"]: methodBuffer.write("\nproc " & sym & EM & pars.plist) methodBuffer.writeLine(" {.\n importc, ", libprag, ".}") if sym.startsWith("vte_regex_unref"): # this function is special, as it returns the object @@ -1405,6 +1438,13 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo) = if (gFunctionInfoGetFlags(mInfo).int and GIFunctionInfoFlags.IS_CONSTRUCTOR.int) != 0 or isProxyCandidate(ret2): if info != nil: asym = asym.replace("new", "new" & manglename(gBaseInfoGetName(info))) + else: # i.e. "pango_attr_background_new" + if asym.endsWith("New"): + asym.setLen(asym.len - 3) + asym[0] = asym[0].toUpperAscii + asym = "new" & asym + #discard #asym = + #if sym == "pango_attr_background_new": echo "sss", info != nil, asym asym = fixedProcNames.getOrDefault(sym, asym) # BLOCKMARK8 : 3 cases: constructor, isProxyCandidate(ret2), plain else if (gFunctionInfoGetFlags(mInfo).int and GIFunctionInfoFlags.IS_CONSTRUCTOR.int) != 0: @@ -1430,7 +1470,8 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo) = if itt == 2: depStr = " {.deprecated.}" methodBuffer.writeLine(hhh & depStr & " =") - methodBuffer.write(pars.tempRes) # caution, gobjectTemp() may write tempRes too. + if not isGObject: + methodBuffer.write(pars.tempRes) # caution, gobjectTemp() may write tempRes too. methodBuffer.write(pars.arrLex) methodBuffer.write(sss) if gCallableInfoCanThrowGerror(minfo): @@ -1620,7 +1661,8 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo) = #assert not (v[1].flags.contains(RecResFlag.zeroTerminated)) methodBuffer.writeLine(" var $1x$2: array[$3, pointer]" % [StringArrayName, multi, $StringArrayEntries]) methodBuffer.writeLine(" var $1$2: cstringArray = cast[cstringArray](addr $1x$2)" % [StringArrayName, multi]) - methodBuffer.writeLine(" var $1_00 = seq2$2($3, $4$5)" % [k, v[0].replace("00").capitalizeAscii, k, StringArrayName, multi]) + methodBuffer.writeLine(" var $1_00 = seq2$2($3, $4$5)" % [k, v[0].replace("00").capitalizeAscii, k, + StringArrayName, multi]) if multi == "": multi = "1" elif v[1].flags.contains(RecResFlag.unamedA): @@ -1644,17 +1686,47 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo) = methodBuffer.writeLine(" result = glistObjects2seq($1, resul0, $2)" % [ngrRet.childName, $elTransferFull]) if ngrRet.infoType == GIInfoType.STRUCT: if gBaseInfoGetName(gTypeInfoGetInterface(gTypeInfoGetParamType(ret2, 0))) notin ["Atom", "Variant"]: # care later - methodBuffer.writeLine(" result = glistStructs2seq[$1](resul0)" % [ngrRet.childName]) + methodBuffer.writeLine(" result = glistStructs2seq[$1](resul0, $2)" % [ngrRet.childName, $( + gCallableInfoGetCallerOwns(minfo) != GITransfer.EVERYTHING)]) if gCallableInfoGetCallerOwns(minfo) != GITransfer.NOTHING: - methodBuffer.writeLine(" g_list_free(resul0)") + methodBuffer.writeLine(" g_list_free(resul0)") # only free the list itself, the elements are moved to the seq else: if ngrRet.infoType == GIInfoType.OBJECT or ngrRet.infoType == GIInfoType.INTERFACE: if ngrRet.infoType != GIInfoType.INTERFACE: assert findFreeObject(gTypeInfoGetInterface(gTypeInfoGetParamType(ret2, 0))).len == 0 - methodBuffer.writeLine(" result = glistObjects2seq($1, $2, $3)" % [ngrRet.childName, sym & pars.arglist, $elTransferFull]) + methodBuffer.writeLine(" result = glistObjects2seq($1, $2, $3)" % [ngrRet.childName, sym & pars.arglist, + $elTransferFull]) else: methodBuffer.writeLine(" discard") assert(gCallableInfoGetCallerOwns(minfo) in {GITransfer.EVERYTHING, GITransfer.NOTHING, GITransfer.CONTAINER}) + + elif ngrRet.res == gslist: + var elTransferFull = gCallableInfoGetCallerOwns(minfo) == GITransfer.EVERYTHING + if gCallableInfoCanThrowGerror(minfo) or gCallableInfoMayReturnNull(minfo) or gCallableInfoGetCallerOwns(minfo) != + GITransfer.NOTHING: + methodBuffer.writeLine(" let resul0 = " & sym & pars.arglist) + checkForGerror() + if gCallableInfoMayReturnNull(minfo): + methodBuffer.writeLine(" if resul0.isNil:") + methodBuffer.writeLine(" return") + if ngrRet.infoType == GIInfoType.OBJECT or ngrRet.infoType == GIInfoType.INTERFACE: + methodBuffer.writeLine(" result = gslistObjects2seq($1, resul0, $2)" % [ngrRet.childName, $elTransferFull]) + if ngrRet.infoType == GIInfoType.STRUCT: + if gBaseInfoGetName(gTypeInfoGetInterface(gTypeInfoGetParamType(ret2, 0))) notin ["Atom", "Variant"]: # care later + methodBuffer.writeLine(" result = gslistStructs2seq[$1](resul0, $2)" % [ngrRet.childName, $( + gCallableInfoGetCallerOwns(minfo) != GITransfer.EVERYTHING)]) + if gCallableInfoGetCallerOwns(minfo) != GITransfer.NOTHING: + methodBuffer.writeLine(" g_slist_free(resul0)") # only free the list itself, the elements are moved to the seq + else: + if ngrRet.infoType == GIInfoType.OBJECT or ngrRet.infoType == GIInfoType.INTERFACE: + if ngrRet.infoType != GIInfoType.INTERFACE: + assert findFreeObject(gTypeInfoGetInterface(gTypeInfoGetParamType(ret2, 0))).len == 0 + methodBuffer.writeLine(" result = gslistObjects2seq($1, $2, $3)" % [ngrRet.childName, sym & pars.arglist, + $elTransferFull]) + else: + methodBuffer.writeLine(" discard") + assert(gCallableInfoGetCallerOwns(minfo) in {GITransfer.EVERYTHING, GITransfer.NOTHING, GITransfer.CONTAINER}) + elif ngrRet.flags.contains(RecResFlag.unamedA) and ngrRet.res == intAX: # https://discourse.gnome.org/t/g-key-file-get-integer-list-transfer-container/4729 # for fundamental types GITransfer.EVERYTHING and GITransfer.CONTAINER are equivalent @@ -1924,6 +1996,7 @@ proc writeUnion(info: GIUnionInfo) = output.writeLine(" ", mangleName(gBaseInfoGetName(info)) & EM & " {.pure, byRef, union.} = object") else: output.writeLine(" ", mangleName(gBaseInfoGetName(info)) & "00" & EM & " {.pure, union.} = object") + if WriteFields or callerAlloc.contains(($gBaseInfoGetNamespace(info)).toLowerAscii & '.' & mangleName(gBaseInfoGetName(info))): for j in 0.cint ..< info.gUnionInfoGetNFields(): let field = info.gUnionInfoGetField(j) @@ -2006,6 +2079,7 @@ proc keymapKeyArrayToSeq(s: ptr KeymapKey; n: int): seq[KeymapKey] = """ % [$StringArrayEntries] proc writeStruct(info: GIStructInfo) = + if gStructInfoIsGtypeStruct(info) and gBaseInfoGetName(info) != "ObjectClass": return # we should not need the class and interface structs if gBaseInfoGetName(info).endsWith("Private"): # since v0.5.3 we do not write private structs @@ -2017,15 +2091,26 @@ proc writeStruct(info: GIStructInfo) = output.writeLine(" " & mangleName(gBaseInfoGetName(info)) & "00" & EM & " = Event00") output.writeLine(" " & mangleName(gBaseInfoGetName(info)) & EM & " = Event") return - if callerAlloc.contains(($gBaseInfoGetNamespace(info)).toLowerAscii & '.' & mangleName(gBaseInfoGetName(info))): + + if gBaseInfoGetName(info) == "Address" and moduleNamespace == "nice": + #import nativesockets + # See https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/agent/address.h + output.writeLine(" Address* {.pure, byRef, union.} = object") + output.writeLine(" `addr`*: SockAddr") + output.writeLine(" ip4*: Sockaddr_in") + output.writeLine(" ip6*: Sockaddr_in6") + + elif callerAlloc.contains(($gBaseInfoGetNamespace(info)).toLowerAscii & '.' & mangleName(gBaseInfoGetName(info))): output.writeLine(" ", mangleName(gBaseInfoGetName(info)) & EM & " {.pure, byRef.} = object") else: output.writeLine(" ", mangleName(gBaseInfoGetName(info)) & "00" & EM & " {.pure.} = object") if WriteFields or callerAlloc.contains(($gBaseInfoGetNamespace(info)).toLowerAscii & '.' & mangleName(gBaseInfoGetName( info))) or mangleName(gBaseInfoGetName(info)) == "TargetEntry" or - ["gst.Message", "gst.MiniObject", "glib.List"].contains(($gBaseInfoGetNamespace(info)).toLowerAscii & '.' & mangleName( - gBaseInfoGetName(info))): + ["gst.Message", "gst.MiniObject", "glib.List", "pango.Attribute", "nice.Address", "nice.Candidate"].contains(( + $gBaseInfoGetNamespace(info)).toLowerAscii & '.' & mangleName(gBaseInfoGetName(info))): + if gBaseInfoGetName(info) == "Address": + echo "kkk", info.gStructInfoGetNFields() for j in 0.cint ..< info.gStructInfoGetNFields(): let field = info.gStructInfoGetField(j) let name = manglename(gBaseInfoGetName(field)) & EM @@ -2053,6 +2138,11 @@ proc writeStruct(info: GIStructInfo) = if mangleName(gBaseInfoGetName(info)) == "List" and moduleNamespace == "glib": output.writeLine("\nproc g_list_free*(list: ptr glib.List) {.importc: \"g_list_free\", libprag.}") output.writeLine("\nproc g_list_prepend*(list: ptr glib.List; data: pointer): ptr glib.List {.importc: \"g_list_prepend\", libprag.}") + + if mangleName(gBaseInfoGetName(info)) == "SList" and moduleNamespace == "glib": + output.writeLine("\nproc g_slist_free*(list: ptr glib.SList) {.importc: \"g_slist_free\", libprag.}") + output.writeLine("\nproc g_slist_prepend*(list: ptr glib.SList; data: pointer): ptr glib.SList {.importc: \"g_slist_prepend\", libprag.}") + if mangleName(gBaseInfoGetName(info)) == "TargetEntry": # assert false executed for gtk3 output.writeLine(TargetEntryProx) @@ -2066,7 +2156,9 @@ proc writeStruct(info: GIStructInfo) = genDestroyFreeUnref() template writeSignal() = + #echo gBaseInfoGetName(signalInfo), gCallableInfoGetNArgs(signalInfo), gTypeInfoGetTag(zzzu) if gCallableInfoGetNArgs(signalInfo) > 0 or gTypeInfoGetTag(zzzu) != GITypeTag.VOID: + #echo "aaa", gBaseInfoGetName(signalInfo) var memo = "" memo.addInt(gCallableInfoGetNArgs(signalInfo)) memo.add(RecSep) @@ -2109,6 +2201,9 @@ template writeSignal() = signalbuffer.write(" g_signal_connect_data(self.impl, \"") signalbuffer.write($gBaseInfoGetName(signalInfo)) signalbuffer.writeLine("\", cast[GCallback](p), xdata, nil, cf)") + if false: #gBaseInfoGetName(signalInfo) == "invalidate-size": + echo "aha" + signalbuffer.writeLine("aha") proc writeInterface(info: GIInterfaceInfo) = if not suppressType: @@ -2467,6 +2562,10 @@ proc writeConst(info: GIConstantInfo) = var h = $gBaseInfoGetName(info) if h == "MODIFIER_MASK": return + + if h == "ATTR_INDEX_FROM_TEXT_BEGINNING": + return # gint in gir, but should be guint + if h.replace("_", "").toLowerAscii == "keych": h.add(str[^5]) if gdkKeys.contains(h.toLowerAscii): @@ -2692,7 +2791,35 @@ proc glistObjects2seq*[T](t: typedesc[T]; l: ptr glib.List; elTransferFull: bool g_object_set_qdata(r.impl, Quark, addr(r[])) el = el.next -proc glistStructs2seq*[T](l: ptr glib.List): seq[T] = +proc gslistObjects2seq*[T](t: typedesc[T]; l: ptr glib.SList; elTransferFull: bool): seq[T] = + var r: T + var obj: ptr Object00 + var el = l + while el != nil: + obj = cast[ptr Object00](el.data) + if obj == nil: + continue + let qdata = g_object_get_qdata(obj, Quark) + if qdata != nil: + r = cast[T](qdata) + assert(r.impl == obj) + result.add(r) + else: + fnew(r, finalizeGObject)#" % [mprefix]) + r.impl = obj + GC_ref(r) + if elTransferFull: + if g_object_is_floating(r.impl).int != 0: + discard g_object_ref_sink(r.impl) + else: + discard g_object_ref_sink(r.impl) + g_object_add_toggle_ref(r.impl, toggleNotify, addr(r[])) + g_object_unref(r.impl) + assert(g_object_get_qdata(r.impl, Quark) == nil) + g_object_set_qdata(r.impl, Quark, addr(r[])) + el = el.next + +proc glistStructs2seq*[T](l: ptr glib.List; igFin: bool): seq[T] = var r: T var el = l while el != nil: @@ -2700,6 +2827,20 @@ proc glistStructs2seq*[T](l: ptr glib.List): seq[T] = continue newWithFinalizer(r) r.impl = cast[typeof(r.impl)](el.data) + r.ignoreFinalizer = igFin + result.add(r) + el = el.next + +proc gslistStructs2seq*[T](l: ptr glib.SList; igFin: bool): seq[T] = + var r: T + var el = l + while el != nil: + if el.data == nil: + continue + newWithFinalizer(r) + r.impl = cast[typeof(r.impl)](el.data) + r.ignoreFinalizer = igFin + result.add(r) el = el.next #proc seq2GList*[T](s: seq[T]): ptr glib.List = @@ -2720,7 +2861,21 @@ proc seq2GList*[T](s: seq[T]): ptr glib.List = discard # make it compile for these cases, of course will not work return l +proc seq2GSList*[T](s: seq[T]): ptr glib.SList = + var l: ptr glib.SList + var i = s.len + while i > 0: + dec(i) + when T is gobject.Object: # or T is pango.Item: + l = g_slist_prepend(l, s[i].impl) + elif T is cstring: + l = g_slist_prepend(l, s[i]) # cstring + else: + discard # make it compile for these cases, of course will not work + return l + #include gimplgobj +include gimplglib """ @@ -2743,13 +2898,40 @@ proc fromStringStructure*(string: cstring; `end`: var string): Structure = """ +const Pango_EPI = """ + +when not declared(ATTR_INDEX_TO_TEXT_END): + const ATTR_INDEX_TO_TEXT_END* = uint32.high + +when not declared(ATTR_INDEX_FROM_TEXT_BEGINNING): + const ATTR_INDEX_FROM_TEXT_BEGINNING* = uint32.low + +proc setStartIndex*(a: Attribute; i: uint32) = + a.impl.startIndex = i + +proc setEndIndex*(a: Attribute; i: uint32) = + a.impl.endIndex = i + +proc setIndices*(a: Attribute; s, e: uint32) = + a.impl.startIndex = s + a.impl.endIndex = e + +proc `startIndex=`*(a: Attribute; i: uint32) = + a.impl.startIndex = i + +proc `endIndex=`*(a: Attribute; i: uint32) = + a.impl.endIndex = i + +#proc `indices=`*(a: Attribute; s, e: uint32) = +proc `indices=`*(a: Attribute; se: tuple[start: uint32; `end`: uint32]) = + a.impl.startIndex = se[0] + a.impl.endIndex = se[1] + +""" + const Nice_EPI = """ -proc nice_agent_attach_recv*(self: ptr Object00; streamID: cuint; componentID: cuint; ctx: ptr glib.MainContext00; - fn: AgentRecvFunc; data: pointer): gboolean {.importc, libprag.} - -proc attachRecv*(self: Agent; streamID: int; componentID: int; ctx: MainContext; fn: AgentRecvFunc; data: pointer): bool = - toBool(nice_agent_attach_recv(self.impl, streamID.cuint, componentID.cuint, ctx.impl, fn, data)) +include gimplnice """ @@ -2762,6 +2944,8 @@ when not declared(g_thread_new): fnew(result, finalizerunref) result.impl = g_thread_new(name, fn, data) +#include gimplglib will not work as glib does not import gobject + """ const GTK3_EPI = """ @@ -3047,6 +3231,10 @@ proc cstringArrayToSeq*(s: ptr cstring): seq[string] = output.writeLine("import glib\n") elif namespace == "Gst": discard + elif namespace == "Nice": + output.writeLine("import macros, nativesockets\n") + output.writeLine("from strutils import `%`\n") + discard if namespace == "GObject": output.writeLine("\nimport times") output.writeLine("var qt = \"NGIQ\" & $epochTime()") @@ -3342,7 +3530,9 @@ proc get$1*(builder: Builder; name: string): $1 = fnew(result, $3.finalizeGObject) result.impl = gobj result.ignoreFinalizer = true - g_object_add_toggle_ref(result.impl, toggleNotify, addr(result[])) + GC_ref(result) # as following g_object_unref() call will also execute a GC_unref(result) call + g_object_add_toggle_ref(result.impl, toggleNotify, addr(result[])) # do toggle_refs make sense at all for builder objects? + g_object_unref(result.impl) # new for v0.8.4 and GTK4, make close main windows working assert(g_object_get_qdata(result.impl, Quark) == nil) g_object_set_qdata(result.impl, Quark, addr(result[])) assert(toBool(g_type_check_instance_is_a(cast[ptr TypeInstance00](result.impl), gt))) @@ -3359,6 +3549,9 @@ proc get$1*(builder: Builder; name: string): $1 = if namespace == "Gst": output.write(Gst_EPI) + if namespace == "Pango": + output.write(Pango_EPI) + if namespace == "GLib": output.write(GLib_EPI) @@ -3522,7 +3715,7 @@ launch() # if not xcallerAlloc.contains(el): # echo el -# 3525 lines +# 3717 lines # gtk_icon_view_get_tooltip_context bug Candidate # gtk_tree_view_get_cursor bug #