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

nice_agent_get_selected_pair in gintro/nice is declared incorrectly #99

Closed
demotomohiro opened this issue Nov 5, 2020 · 19 comments
Closed

Comments

@demotomohiro
Copy link
Contributor

Thank you for supporting libnice!

libnice has nice-agent-get-selected-pair function and local and remote parameters are NiceCandidate ** type.
https://libnice.freedesktop.org/libnice/NiceAgent.html#nice-agent-get-selected-pair

But local and remote parameters are declared as ptr Candidate00 in gintro/nice and cannot get pointer to NiceCandidate.
This is gintro/nice code that wrap nice-agent-get-selected-pair function.

proc nice_agent_get_selected_pair(self: ptr Agent00; streamId: uint32; componentId: uint32;
    local: ptr Candidate00; remote: ptr Candidate00): gboolean {.
    importc, libprag.}

proc getSelectedPair*(self: Agent; streamId: int; componentId: int;
    local: Candidate; remote: Candidate): bool =
  toBool(nice_agent_get_selected_pair(cast[ptr Agent00](self.impl), uint32(streamId), uint32(componentId), cast[ptr Candidate00](local.impl), cast[ptr Candidate00](remote.impl)))

nice_agent_get_selected_pair function returns the address of existing NiceCandidate object by writing it to local and remote arguments.
So it may not be freed by caller.
You can see how to use nice_agent_get_selected_pair in cb_component_state_changed function in this libnice C example code.

libnice doesn't provide functions to query a value of NiceCandidate's fields.
So I need to access fields of NiceCandidate directly to get an address or other data in NiceCandidate.
In cb_component_state_changed and print_local_data functions in simple-example.c in libnice, it reads NiceCandidate's fields directly.
https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/examples/simple-example.c
But Candidate00 type in gintro/nice wraps NiceCandidate type without fields.

I think this can be fixed by adding following type in gintro/nice:

# winlean and posix module exports SockAddr, Sockaddr_in and Sockaddr_in6 types.
when defined(windows):
  import winlean
else:
  import posix

type
  # See https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/agent/address.h
  NiceAddress* {.union.} = object
    `addr`*: SockAddr
    ip4*: Sockaddr_in
    ip6*: Sockaddr_in6

  # See https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/agent/candidate.h
  NiceCandidate* {.pure.} = object
    `type`*: CandidateType
    transport*: CandidateTransport
    `addr`*: NiceAddress
    baseAddr*: NiceAddress
    priority*: uint32
    streamId*: cuint
    componentId*: cuint
    foundation*: array[0 .. (CANDIDATE_MAX_FOUNDATION.int - 1), char]
    username*: cstring
    password*: cstring

and declare nice_agent_get_selected_pair like this:

proc nice_agent_get_selected_pair(self: ptr Agent00; streamId: uint32; componentId: uint32;
    local: ptr ptr NiceCandidate; remote: ptr ptr NiceCandidate): gboolean {.
    importc, libprag.}

and getSelectedPair like this:

proc getSelectedPair*(self: Agent; streamId: int; componentId: int;
    local: var ptr NiceCandidate; remote: var ptr NiceCandidate): bool =
  toBool(nice_agent_get_selected_pair(cast[ptr Agent00](self.impl), uint32(streamId), uint32(componentId), addr local, addr remote))

or

proc getSelectedPair*(self: Agent; streamId: int; componentId: int;
    local: var Candidate; remote: var Candidate): bool =
  assert local.impl == nil
  assert remote.impl == nil

  toBool(nice_agent_get_selected_pair(cast[ptr Agent00](self.impl), uint32(streamId), uint32(componentId), cast[ptr ptr Candidate00](addr local.impl), cast[ptr ptr Candidate00](addr remote.impl)))

  local.ignoreFinalizer = true
  remote.ignoreFinalizer = true
@StefanSalewski
Copy link
Owner

Thanks for reporting. I don't know much about libnice myself but I will try to investigate that issue soon.

@StefanSalewski
Copy link
Owner

OK, you may try with nimble install gintro@#head

For this issue it seems that the bug was only that NiceCandidate was not marked as "light" object which needs no proxy object. We have a list of that entities in gen.nim generator script and I added one line for this fix. So that type has fields now, and we generally use plain stack variables of that type and do not allocate it with a function on the heap. I hope it will work for you now. (We can not avoid these need for manually fixes, I had a discussion with Mr. Droege from Rust team about these issues.)

And we have support for optional out var parameters now. That was some more work, and it is not much tested yet. A lot of code has changed. There is at least one known bug -- a few procs have optional seq parameter, that would crash if no value is provided. We will fix later.

@demotomohiro
Copy link
Contributor Author

Thank you for your fix!
But it still doesn't work.
This is gintro generated code that define Candidate type with Address00 type:

type
  Candidate* {.pure, byRef.} = object
    `type`*: CandidateType
    transport*: CandidateTransport
    `addr`*: Address00
    baseAddr*: Address00
    priority*: uint32
    streamId*: uint32
    componentId*: uint32
    foundation*: array[33, int8]
    username*: cstring
    password*: cstring

And this is gintro generated code that define Address00 type and Address type:

type
  Address00* {.pure.} = object
  Address* = ref object
    impl*: ptr Address00
    ignoreFinalizer*: bool

nice module exports procs that takes Address but it doesn't export procs that takes Address00.
And I cannot use procs that get port or address string from Address to addr or baseAddr fields of Candidate type.
I think NiceAddress is also need to be marked as "light" object like NiceCandidate.

This is gintro generated code that declares nice_agent_get_selected_pair proc and getSelectedPair proc.

proc nice_agent_get_selected_pair(self: ptr Agent00; streamId: uint32; componentId: uint32;
    local: Candidate; remote: Candidate): gboolean {.
    importc, libprag.}

proc getSelectedPair*(self: Agent; streamId: int; componentId: int;
    local: Candidate; remote: Candidate): bool =
  toBool(nice_agent_get_selected_pair(cast[ptr Agent00](self.impl), uint32(streamId), uint32(componentId), local, remote))

But nice_agent_get_selected_pair function has local and remote paramaters as NiceCandidate ** (pointer to pointer to NiceCandidate) type.
https://libnice.freedesktop.org/libnice/NiceAgent.html#nice-agent-get-selected-pair

nice_agent_get_selected_pair function returns the address of existing NiceCandidate object by writing it to local and remote arguments.
So when I use nice_agent_get_selected_pair function to get NiceCandidate, I have to declare 2 pointers to NiceCandidate and pass addresses of these 2 pointers to nice_agent_get_selected_pair.

Thus, nice_agent_get_selected_pair proc and getSelectedPair proc should be declared like this:

proc nice_agent_get_selected_pair(self: ptr Agent00; streamId: uint32; componentId: uint32;
    local: var ptr Candidate; remote: var ptr Candidate): gboolean {.
    importc, libprag.}

proc getSelectedPair*(self: Agent; streamId: int; componentId: int;
    local: var ptr Candidate; remote: var ptr Candidate): bool =
  toBool(nice_agent_get_selected_pair(cast[ptr Agent00](self.impl), uint32(streamId), uint32(componentId), local, remote))

And getSelectedPair is used like this:

var local, remote: ptr Candidate
let ret = agent.getSelectedPair(streamId, componentId, local.addr, remote.addr)
let port = local.`addr`.port
# local and remote must not be freed.

@StefanSalewski
Copy link
Owner

OK, I have finished the GtkLabel example in the GTK4 book finally (http://ssalewski.de/gtkprogramming.html#_label) so I can work again on your issue.

You are right of course, the nice.Address data type has to be a "light" type with fields and without a proxy object. That type is reported by gobject-introspection as struct with zero fields. But it has a field of type union. So we have to create that type manually. All that is not that easy and straight forward, so I will convert your linked C code example from above to Nim and test it myself. I will try to do it in the next few days.

@StefanSalewski
Copy link
Owner

I will convert your linked C code example

That is really hard :-)

I think I will finally get it working, but I wonder if it really makes much sense to write so much low level code tightly coupled to a low level C lib in Nim.

@demotomohiro
Copy link
Contributor Author

nice.Address type can be defined like this in Nim:

import nativesockets

type
  # See https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/agent/address.h
  Address* {.union.} = object
    `addr`*: SockAddr
    ip4*: Sockaddr_in
    ip6*: Sockaddr_in6

SockAddr, Sockaddr_in and Sockaddr_in6 are defined in winlean module on windows and in posix module in other os.
And nativesockets module export these types from winlean or posix module.

nice.Candidate object type contains nice.Address type.
I think Nim compiler need to know sizeof nice.Address so that memory layout of nice.Candidate object type in Nim and NiceCandidate type in libnice C code are match and we can access each fields of nice.Candidate correctly.

If libnice provided setters and getters to access each fields of NiceCandidate like void nice_candidate_set_addr(NiceCandidate *candidate, const NiceAddress *addr) or NiceAddress* nice_candidate_get_addr(const NiceCandidate *candidate), we would not need to define each fields of nice.Address or nice.Candidate.

@StefanSalewski
Copy link
Owner

Thanks.

Yesterday I tried to get GSList support working, as that C example used GSList. Have still to test. nice.Candidate in now again a heavy object with proxy, but for Candidate00 we generate fields, so we can access fields like cand.impl.priority = uint32(parseInt(tokens[1])). Ugly, but we can create access functions manually later.

Then there is glib.ioAddWatch() which adds a callback, I think I have to create a macro for this.

I hope I will get is working this weekend.

@StefanSalewski
Copy link
Owner

OK, you can try again :-)

The example program

https://github.com/StefanSalewski/gintro/blob/master/examples/gtk3/simple_example.nim

looks fine now, compiles and seems to have similar output as C version. But again I can not test on windows or test real function of that program as I have currently only one Linux computer available.

I added some new macros in files gimpglib.nim and gimplnice.nim. Macros is always hard, seems to work, but maybe you can improve them yourself. And you may test with and without the last optional parameter and for different types of the optional parameter. I have not done all that tests. For NiceAddress I used your suggestion, and I had some discussion with Mr. Droege, see https://discourse.gnome.org/t/size-of-nicecandidate-of-libnice/4913

@demotomohiro
Copy link
Contributor Author

demotomohiro commented Dec 4, 2020

Thank you for your hard work!

I tried to run simple_example.nim but parsing remote candidate generate error.
I will try to fix it and send PR.

You can test with only one computer.
Open 2 consoles and run example program on each console.
one program need to be run with argument "0" and another program run with argument "1".
Then, copy output text to text editor, make it one line and copy it to another console.
If you use Vim, you can remove new lines without inserting space with gJ after selecting text with visual mode.

If you run 2 libnice programs at same time on one PC, they choose different port (looks like they choose port randomly) and they can communicate each other after they got remote candidates.

@StefanSalewski
Copy link
Owner

Thanks for instructions to test it. I have just launched the C version, that one works. Will try to fix the Nim version today.

@StefanSalewski
Copy link
Owner

Found a few typos.

Main bug is use of

grep -A20 "seq2GSList\*" ~/.nimble/pkgs/gintro-#head/gintro/gobject.nim 
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

I think it was never tested before, and obviously do not work for NiceCandidate as NiceCandidate in not a GObject.

Will try to fix it today. If I remember correctly I used the discard statement because it was unclear which objects have the impl field, so I restrict to strings and GObjects that time.

@StefanSalewski
Copy link
Owner

I have fixed the typos, but when I try to make seq2GSList() working compile fails with message:

Error: template instantiation too nested

That is very ugly, I have no idea which really causes that error.

For now I will send you the file with fixed typos:

## 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)
        break # missing in initial release!
      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(x, streamId)
        if c == nil:
          gMsg("failed to parse candidate: " & x)
          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:")
  stdout.write("\n  ")
  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()

Was mainly some small typos and a missing break statement:

diff simple_example.nim ~/gintro/examples/gtk3/simple_example.nim 
71c71
<     stdout.write(localUfrag, ' ', localPassword)
---
>     stdout.write(localUfrag, localPassword)
75c75
<       stdout.write(' ', cast[cstring](addr el.impl.foundation), ',', el.impl.priority, ',', ipaddr, ',', getPort(el.impl.`addr`),
---
>       stdout.write(' ', cast[cstring](addr el.impl.foundation), ',', el.impl.priority, ',', ipaddr, ' ', getPort(el.impl.`addr`),
104d103
<         break # missing in initial release!
142c141
<         var c: nice.Candidate = parseCandidate(x, streamId)
---
>         var c: nice.Candidate = parseCandidate(lineArgv[i], streamId)
144c143
<           gMsg("failed to parse candidate: " & x)
---
>           gMsg("failed to parse candidate: " & lineArgv[i])
185c184
<   stdout.write("\n  ")
---
>   echo("  ")

But with

grep -A20 "seq2GSList\*" ~/.nimble/pkgs/gintro-#head/gintro/gobject.nim 
proc seq2GSList*[T](s: seq[T]): ptr glib.SList =
  var l: ptr glib.SList
  var i = s.len
  while i > 0:
    dec(i)
    when true:#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

it fails to compile with

Error: template instantiation too nested

Will ask on Nim forum and try again later. Only idea is to try a non generic seq2GSList for NiceCandidate in nice.nim module. Will maybe try later.

@StefanSalewski
Copy link
Owner

Well I found two more errors, both will take some time to fix. Will try to do it tomorrow.

@demotomohiro
Copy link
Contributor Author

I have fixed the typos, but when I try to make seq2GSList() working compile fails with message:

Error: template instantiation too nested

That is very ugly, I have no idea which really causes that error.

I don't get that compile error.
I fixed seq2GSList like following code.
Any types that has impl field are prepend with g_slist_prepend(l, s[i].impl)
And when T doesn't match any supported type, it is compile error.
It worked with my test code.

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 compiles(T.impl):
      l = g_slist_prepend(l, s[i].impl)
    elif T is cstring:
      l = g_slist_prepend(l, s[i]) # cstring
    else:
      {.error: "seq2GSList was called with unsupported type".}
  return l

@demotomohiro
Copy link
Contributor Author

getSelectedPair proc in gintro/nice is defined incorrectly.
This is how getSelectedPair and nice_agent_get_selected_pair are defined:

proc nice_agent_get_selected_pair(self: ptr Agent00; streamId: uint32; componentId: uint32;
    local: ptr Candidate00; remote: ptr Candidate00): gboolean {.
    importc, libprag.}

proc getSelectedPair*(self: Agent; streamId: int; componentId: int;
    local: Candidate; remote: Candidate): bool =
  toBool(nice_agent_get_selected_pair(cast[ptr Agent00](self.impl), uint32(streamId), uint32(componentId), cast[ptr Candidate00](local.impl), cast[ptr Candidate00](remote.impl)))

They need to be fixed like this:

proc nice_agent_get_selected_pair(self: ptr Agent00; streamId: uint32; componentId: uint32;
    local: var ptr Candidate00; remote: var ptr Candidate00): gboolean {.
    importc, libprag.}

proc getSelectedPair*(self: Agent; streamId: int; componentId: int;
    local: Candidate; remote: Candidate): bool =
  toBool(nice_agent_get_selected_pair(cast[ptr Agent00](self.impl), uint32(streamId), uint32(componentId), local.impl, remote.impl))

It is used like this code:

var
  local = Candidate(ignoreFinalizer: true)
  remote = Candidate(ignoreFinalizer: true)
if getSelectedPair(agent, streamId, componentId, local, remote):

nice_agent_get_selected_pair function in libnice returns pointers to NiceCandidate object by taking pointers to a pointer to a NiceCandidate object (it is not a pointer to NiceCandidate) and write an address of NiceCandidate object to the pointer that given pointer to pointer to NiceCandidate points to.
https://libnice.freedesktop.org/libnice/NiceAgent.html#nice-agent-get-selected-pair

@StefanSalewski
Copy link
Owner

I told you I will fix it. Done.

Works for me. Please test with

nimble install gintro@#head

@demotomohiro
Copy link
Contributor Author

Thank you!

gintro/examples/gtk3/simple_example.nim worked on termux.
But it doesn't work on windows.

libnice_simple_test.nim in https://github.com/StefanSalewski/gintro/pull/103/files worked on both windows and termux.
So I think nice module is fine but simple_example.nim need to be fix.
I will try to fix it on windows and send PR.

@StefanSalewski
Copy link
Owner

Unfortunately I can not help you for your Windows issue, I have no idea. One reason could be that gintro does not work for 32 bit OS, I think no one ever has tested gintro with 32 bit. But I assume that your windows is 64 bit.

For the "Error: template instantiation too nested" I found the reason, it is the "when (compiles do: import gintro/gtk):" statement. That one seems to be very broken, see

https://forum.nim-lang.org/t/7222

I will add your libnice_simple_test.nim to the gtk3 examples tomorrow.

@demotomohiro
Copy link
Contributor Author

It seems we don't need to check existance of gintro/gtk module.
When writing command line program that doesn't use gtk, importing gintro/dummygtk module works even if there is gintro/gtk module.

I have installed gtk3 and gtk4 using (MSYS2)[https://www.msys2.org] with following command:

pacman -S mingw-w64-x86_64-gtk3 mingw-w64-x86_64-gtk4

Then I reinstalled gintro with nimble install gintro@#head and I got gintro/gtk and gintro/gtk4 module.
I can build and run examples/gtk3/t0.nim, examples/gtk3/app0.nim, examples/gtk4/app0.nim and examples/gtk4/textview.nim.

I changed following import statements in examples/gtk3/simple_example.nim

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

to

import gintro/[dummygtk, glib, gobject, gio, nice] # For windows with glib but no gtk

and it compils and works without error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants