diff --git a/README.adoc b/README.adoc index 26615ca..a4c9a97 100644 --- a/README.adoc +++ b/README.adoc @@ -31,7 +31,12 @@ Array types. In the past we used that names to indicate array parameters, it was it was ugly using names to indicates data types. So we fixed that. And we have added some better support for GList parameter. GLists are now converted to Nim seqs and vice versa. Not everywhere still, and this conversion is still untested, as GList parameters are used most of the time in exotic functions only. Another change is that -we have support for libnice by a request of a user from Japan. We added the sdp_example.nim converted from +we have support for libnice by a request of a user from Japan now. As he intents to use libnice on Windows 8.1 +with only glib and gobject installed, but with no gtk installed, we had to break module gimpl.nim +into two units called gimplgobj.nim and gimplgtk.nim, and we added a module called dummygtk.nim +which can be imported to make the connect() macro available while a real gtk module is not available. +Please let us know if these changes should break something for ordinary gtk users. +To test libnice we added the sdp_example.nim converted from a C example, it seems to compile and run. And finally with v0.8.3 the examples from the GTK4 book should work. For the GTK3 examples we had to do some minimal fixes, so you may have to do similar fixes for your own code as well. For gintro/examples/gtk3 this was diff --git a/examples/gtk3/sdp_example.nim b/examples/gtk3/sdp_example.nim index 3980e35..0984fdd 100644 --- a/examples/gtk3/sdp_example.nim +++ b/examples/gtk3/sdp_example.nim @@ -9,12 +9,22 @@ # ./sdp-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }') # ./sdp-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }') -import gintro/[gtk, glib, gobject, nice] -import gintro/gio except stdin + +# 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 `%` from os import paramCount, paramStr from strutils import parseInt +when defined(windows): + import winlean +else: + discard # import posix + var gloop: glib.MainLoop stunAddr: string @@ -70,7 +80,7 @@ proc cbComponentStateChanged(agent: nice.Agent; streamID: int; componentID: int; glib.quit(gloop) # proc nice_agent_attach_recv*(self: ptr Agent00; streamID: cuint; componentID: cuint; ctx: ptr glib.MainContext; -# fn: AgentRecvFunc; data: pointer): gboolean {.importc, libprag.} +# fn: AgentRecvFunc; data: pointer): gboolean {.importc, libprag.} # # this has to be a low level function! # AgentRecvFunc* = proc (agent: ptr Agent00; streamId: uint32; componentId: uint32; len: uint32; @@ -89,10 +99,13 @@ proc exampleThread(data: pointer): pointer {.cdecl.} = line: string sdp: string sdp64: string - #when defined(G_OS_WIN32): - # io_stdin = g_io_channel_win32_new_fd(_fileno(stdin)) - #else: - ioStdin = unixNew(getFileHandle(stdin)) + + # https://gist.github.com/demotomohiro/c9d9ad7e17639c6acb9bd2be7c204f3f + when hostOS == "windows": + ioStdin = win32NewFd(system.stdin.getFileHandle) + else: + ioStdin = unixNew(system.stdin.getFileHandle) + discard setFlags(ioStdin, glib.IOFlags.nonBlock) # Create the nice agent agent = nice.newAgent(glib.getContext(gloop), nice.CompatibilityRfc5245) @@ -141,8 +154,7 @@ proc exampleThread(data: pointer): pointer {.cdecl.} = var s: IOStatus = readLine(ioStdin, line, length, terminatorPos) if s == IOStatus.normal: var sdpLen: uint64 - let sdpxxx = base64Decode(line, sdpLen) #seq[uint8] - sdp = cast[string](sdpxxx) + let sdp = cast[string](base64Decode(line, sdpLen)) #seq[uint8] # Parse remote candidate list and set it on the agent if sdp.len > 0 and parseRemoteSdp(agent, sdp) > 0: break @@ -183,9 +195,9 @@ proc exampleThread(data: pointer): pointer {.cdecl.} = return nil proc main = - echo paramCount() - for i in 0 .. paramCount(): - echo paramStr(i) + #echo paramCount() + #for i in 0 .. paramCount(): + # echo paramStr(i) let argc = paramCount() + 1 var argv = newSeq[string](argc) for i in 0 ..< argc: diff --git a/gintro/.keep b/gintro/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/gintro/dummygtk.nim b/gintro/dummygtk.nim new file mode 100644 index 0000000..35c122c --- /dev/null +++ b/gintro/dummygtk.nim @@ -0,0 +1,6 @@ +import gobject +import macros, strutils + +include gisup3 +include gimplgobj + diff --git a/gintro/gimpl.nim b/gintro/gimplgobj.nim similarity index 70% rename from gintro/gimpl.nim rename to gintro/gimplgobj.nim index 3ccdd99..7d36e5e 100644 --- a/gintro/gimpl.nim +++ b/gintro/gimplgobj.nim @@ -1,5 +1,6 @@ #from sequtils import keepItIf -import sequtils +import macros, sequtils +from strutils import startsWith, replace const RecSep = "!" # Record separator for entries in gisup.nim, also defined in gen.nim # caution: $$ due to string interpolation @@ -359,122 +360,6 @@ $1($4) """ % [$procName, ats, $procNameCdecl, $arg] result = parseStmt(r1s & r2s) -################################################### +# 362 lines -### CellDataFunc, -# see https://developer.gnome.org/gtk3/stable/GtkTreeViewColumn.html#gtk-tree-view-column-set-cell-data-func -# the low level GTK interface: -# proc cellDataFunc(column00: ptr TreeViewColumn00; renderer00: ptr CellRenderer00; -# model00: ptr TreeModel00; iter: TreeIter; data: pointer) {.cdecl.} - -# unset -- the macro can do this already -proc unsetCellDataFunc*(column: TreeViewColumn; renderer: CellRenderer) = - setCellDataFunc(column, renderer, nil, nil, nil) - -# set a function with this parameter list and one more optional argument -# proc cellDataFunc(column: TreeViewColumn; renderer: CellRenderer; model: TreeModel; iter: TreeIter) -# if there is no optional arg, then we make it a pointer with nil value. -# if arg is a ref, then we apply GC_ref() and pass it -# if arg is a ptr, then we pass it. (but we never free that object) -# if arg is a value type, then we replace it with a copy of a ref type to guaranty lifetime -macro setCellDataFunc*(column: TreeViewColumn; renderer: CellRenderer; fn: untyped = nil; arg: typed = nil): untyped = - - if fn == nil: # unset - result = parseStmt("setCellDataFunc($1, $2, nil, nil, nil)" % [$column, $renderer]) - return - - var CdfID {.compileTime, global.}: int # unique id for our generated cell data functions - inc(CdfID) - var ats: string # the type of optional argument as string (arg type string) - var argStr: string # the value of optional argument as string - var atsfix: string # we have to prefix with "ref " if arg is avalue type - if arg == nil: # no optional argument, so fake a nil pointer - argStr = "nil" - ats = "pointer" - else: - argStr = $(arg.toStrLit) - ats = $getTypeInst(arg).toStrLit - - if getTypeInst(arg).typeKind != ntyRef and getTypeInst(arg).typeKind != ntyPtr: - atsFix = "ref " - - let procName = "celldatafunc_" & $CdfID # our init code - let procNameCdecl = "celldatafunc_cdecl_" & $CdfID # the proc that is called from GTK - var procNameDestroy: string # the proc that GTK calls when we unset the cellDataFunc - # first we generate the destroy proc - var r0s: string - - # GTK should unref the optional argument later, so we generate a destroy proc which GTK can call - if arg != nil and getTypeInst(arg).typeKind != ntyPtr: - procNameDestroy = "celldatafunc_destroy" & $CdfID - r0s =""" -proc $1(p: pointer) {.cdecl.} = - # echo "GC_Unref(arg)" - GC_Unref(cast[$2](p)) -""" % [$procNameDestroy, atsFix & ats] - else: - procNameDestroy = "nil" - r0s = "" - -# now we generate the proc which GTK will call for each cell of the treeview. g_object_get_qdata() is -# the GTK function which retrieves the Nim proxy objects from the plain GTK data objects. - var r1s = """ -proc $1(column00: ptr TreeViewColumn00; renderer00: ptr CellRenderer00; - model00: ptr TreeModel00; iter: TreeIter; pdata: pointer) {.cdecl.} = - let column = cast[TreeViewColumn](g_object_get_qdata(column00, Quark)) - let renderer = cast[CellRenderer](g_object_get_qdata(renderer00, Quark)) - let model = cast[TreeModel](g_object_get_qdata(model00, Quark)) -""" % [$procNameCdecl] - if arg == nil: - r1s.add """ - $1(column, renderer, model, iter) # call the user provided Nim proc -""" % [$fn] - elif getTypeInst(arg).typeKind == ntyRef or getTypeInst(arg).typeKind == ntyPtr: - r1s.add """ - let a = cast[$2](pdata) - $1(column, renderer, model, iter, a) # call the user provided Nim proc -""" % [$fn, ats] - else: # arg was a value type, but we pass a copy of ref type - r1s.add """ - let a = cast[ref $2](pdata)[] - $1(column, renderer, model, iter, a) # call the user provided Nim proc -""" % [$fn, ats] - -# this is basically only a code block which is called for initialization, mostly -# making a deep copy of optional argument when argumant is not a ref - var r2s: string - if arg == nil: - r2s =""" -proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = - setCellDataFunc($3, $4, $5, nil, nil) -$1(column, renderer, $6) -""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] - elif getTypeInst(arg).typeKind == ntyPtr: - r2s =""" -proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = - setCellDataFunc($3, $4, $5, cast[pointer](pdata), $7) -$1(column, renderer, $6) -""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] - elif getTypeInst(arg).typeKind == ntyRef: - r2s =""" -proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = - if not pdata.isNil: # or pdata is pointer): - GC_ref(pdata) - setCellDataFunc($3, $4, $5, cast[pointer](pdata), $7) -$1(column, renderer, $6) -""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] - else: # arg is value object - r2s =""" -proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = - var ar: ref $2 - new(ar) - #deepCopy(ar[], pdata) - ar[] = pdata - GC_ref(ar) - setCellDataFunc($3, $4, $5, cast[pointer](ar), $7) -$1(column, renderer, $6) -""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] - - result = parseStmt(r0s & r1s & r2s) -# 429 lines echo diff --git a/gintro/gimplgtk.nim b/gintro/gimplgtk.nim new file mode 100644 index 0000000..29713ad --- /dev/null +++ b/gintro/gimplgtk.nim @@ -0,0 +1,117 @@ +### CellDataFunc, +# see https://developer.gnome.org/gtk3/stable/GtkTreeViewColumn.html#gtk-tree-view-column-set-cell-data-func + +# the low level GTK interface: +# proc cellDataFunc(column00: ptr TreeViewColumn00; renderer00: ptr CellRenderer00; +# model00: ptr TreeModel00; iter: TreeIter; data: pointer) {.cdecl.} + +# unset -- the macro can do this already +proc unsetCellDataFunc*(column: TreeViewColumn; renderer: CellRenderer) = + setCellDataFunc(column, renderer, nil, nil, nil) + +# set a function with this parameter list and one more optional argument +# proc cellDataFunc(column: TreeViewColumn; renderer: CellRenderer; model: TreeModel; iter: TreeIter) +# if there is no optional arg, then we make it a pointer with nil value. +# if arg is a ref, then we apply GC_ref() and pass it +# if arg is a ptr, then we pass it. (but we never free that object) +# if arg is a value type, then we replace it with a copy of a ref type to guaranty lifetime +macro setCellDataFunc*(column: TreeViewColumn; renderer: CellRenderer; fn: untyped = nil; arg: typed = nil): untyped = + + if fn == nil: # unset + result = parseStmt("setCellDataFunc($1, $2, nil, nil, nil)" % [$column, $renderer]) + return + + var CdfID {.compileTime, global.}: int # unique id for our generated cell data functions + inc(CdfID) + var ats: string # the type of optional argument as string (arg type string) + var argStr: string # the value of optional argument as string + var atsfix: string # we have to prefix with "ref " if arg is avalue type + if arg == nil: # no optional argument, so fake a nil pointer + argStr = "nil" + ats = "pointer" + else: + argStr = $(arg.toStrLit) + ats = $getTypeInst(arg).toStrLit + + if getTypeInst(arg).typeKind != ntyRef and getTypeInst(arg).typeKind != ntyPtr: + atsFix = "ref " + + let procName = "celldatafunc_" & $CdfID # our init code + let procNameCdecl = "celldatafunc_cdecl_" & $CdfID # the proc that is called from GTK + var procNameDestroy: string # the proc that GTK calls when we unset the cellDataFunc + # first we generate the destroy proc + var r0s: string + + # GTK should unref the optional argument later, so we generate a destroy proc which GTK can call + if arg != nil and getTypeInst(arg).typeKind != ntyPtr: + procNameDestroy = "celldatafunc_destroy" & $CdfID + r0s =""" +proc $1(p: pointer) {.cdecl.} = + # echo "GC_Unref(arg)" + GC_Unref(cast[$2](p)) +""" % [$procNameDestroy, atsFix & ats] + else: + procNameDestroy = "nil" + r0s = "" + +# now we generate the proc which GTK will call for each cell of the treeview. g_object_get_qdata() is +# the GTK function which retrieves the Nim proxy objects from the plain GTK data objects. + var r1s = """ +proc $1(column00: ptr TreeViewColumn00; renderer00: ptr CellRenderer00; + model00: ptr TreeModel00; iter: TreeIter; pdata: pointer) {.cdecl.} = + let column = cast[TreeViewColumn](g_object_get_qdata(column00, Quark)) + let renderer = cast[CellRenderer](g_object_get_qdata(renderer00, Quark)) + let model = cast[TreeModel](g_object_get_qdata(model00, Quark)) +""" % [$procNameCdecl] + if arg == nil: + r1s.add """ + $1(column, renderer, model, iter) # call the user provided Nim proc +""" % [$fn] + elif getTypeInst(arg).typeKind == ntyRef or getTypeInst(arg).typeKind == ntyPtr: + r1s.add """ + let a = cast[$2](pdata) + $1(column, renderer, model, iter, a) # call the user provided Nim proc +""" % [$fn, ats] + else: # arg was a value type, but we pass a copy of ref type + r1s.add """ + let a = cast[ref $2](pdata)[] + $1(column, renderer, model, iter, a) # call the user provided Nim proc +""" % [$fn, ats] + +# this is basically only a code block which is called for initialization, mostly +# making a deep copy of optional argument when argumant is not a ref + var r2s: string + if arg == nil: + r2s =""" +proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = + setCellDataFunc($3, $4, $5, nil, nil) +$1(column, renderer, $6) +""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] + elif getTypeInst(arg).typeKind == ntyPtr: + r2s =""" +proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = + setCellDataFunc($3, $4, $5, cast[pointer](pdata), $7) +$1(column, renderer, $6) +""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] + elif getTypeInst(arg).typeKind == ntyRef: + r2s =""" +proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = + if not pdata.isNil: # or pdata is pointer): + GC_ref(pdata) + setCellDataFunc($3, $4, $5, cast[pointer](pdata), $7) +$1(column, renderer, $6) +""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] + else: # arg is value object + r2s =""" +proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = + var ar: ref $2 + new(ar) + #deepCopy(ar[], pdata) + ar[] = pdata + GC_ref(ar) + setCellDataFunc($3, $4, $5, cast[pointer](ar), $7) +$1(column, renderer, $6) +""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] + + result = parseStmt(r0s & r1s & r2s) +# 117 lines diff --git a/tests/gen.nim b/tests/gen.nim index ca259af..590fc47 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.3 2020-OCT-21 +# v 0.8.3 2020-OCT-26 # (c) S. Salewski 2018 # usefull for finding death code: @@ -2678,6 +2678,8 @@ proc seq2GList*[T](s: seq[T]): ptr glib.List = discard # make it compile for these cases, of course will not work return l +#include gimplgobj + """ # for v0.8.2 we create this manually, as gst_structure_from_string() is a constructor @@ -2711,11 +2713,12 @@ proc attachRecv*(self: Agent; streamID: int; componentID: int; ctx: MainContext; const GLib_EPI = """ -proc g_thread_new(name: cstring; fn: ThreadFunc; data:pointer): ptr Thread00 {.importc, libprag.} +when not defined(g_thread_new): + proc g_thread_new(name: cstring; fn: ThreadFunc; data:pointer): ptr Thread00 {.importc, libprag.} -proc newThread*(name: string; fn: ThreadFunc; data:pointer): Thread = - fnew(result, finalizerunref) - result.impl = g_thread_new(name, fn, data) + proc newThread*(name: string; fn: ThreadFunc; data:pointer): Thread = + fnew(result, finalizerunref) + result.impl = g_thread_new(name, fn, data) """ @@ -3244,10 +3247,12 @@ proc init* = output.write(GTK_EPI % fixedModName("gtk")) if ISGTK3: output.write("include gisup3\n") - output.write("include gimpl\n") + output.write("include gimplgobj\n") + output.write("include gimplgtk\n") else: output.write("include gisup4\n") - output.write("include gimpl\n") + output.write("include gimplgobj\n") + output.write("include gimplgtk\n") buildableList.add("MenuModel") for i in buildableList: var prefix = "gtk" @@ -3450,7 +3455,7 @@ launch() # if not xcallerAlloc.contains(el): # echo el -# 3453 lines +# 3453 lines include # troubles: gTypeFundamental(gRegisteredTypeInfoGetGType(info)) == G_TYPE_BOXED: #[