lgi-devs / lgi

Dynamic Lua binding to GObject libraries using GObject-Introspection
MIT License
440 stars 70 forks source link

Ability to keep out parameter as NULL #277

Closed Rio6 closed 2 years ago

Rio6 commented 2 years ago

I'm trying to use g_spawn_async_with_pipes with lgi. While the stdin, stdout, and stderr parameters are marked as out, the function behaves differently if they are set to NULL. From the description:

If stdin_pipe_out is NULL, the child’s standard input is attached to /dev/null unless G_SPAWN_CHILD_INHERITS_STDIN is set.

If stderr_pipe_out is NULL, the child’s standard error goes to the same location as the parent’s standard error unless G_SPAWN_STDERR_TO_DEV_NULL is set.

If stdout_pipe_out is NULL, the child’s standard output goes to the same location as the parent’s standard output unless G_SPAWN_STDOUT_TO_DEV_NULL is set.

However, I was not able to get lgi to set those parameters to NULL. Using the following example:

local GLib = require("lgi").GLib

local flags = GLib.SpawnFlags { "SEARCH_PATH", "CLOEXEC_PIPES" }
local pid, stdin, stdout, stderr = GLib.spawn_async_with_pipes(nil, {"echo", "hello"}, nil, flags, nil, nil, nil, nil, nil)
print(pid, stdin, stdout, stderr)

It outputs

23830   5       6       8

rather than

hello
23830   nil       nil       nil

(or something similiar).

I'm not sure if what I'm trying to do is already doable with other methods, but I think there should be a way to do this.

ntd commented 2 years ago

I don't think setting output parameters to NULL is actually possible, see e.g. the LGI documentation: output arguments are always collected and returned after the main return value, hence they are always set.

As a workaround, you can manually create your own custom spawn callable and forcibly set the last 4 arguments to plain input pointers, so they can be explicitely set.

local lgi  = require 'lgi'
local core = require 'lgi.core'
local GLib = lgi.require 'GLib'
local ti   = require 'lgi.ffi'.types

local api = core.gi.GLib.spawn_async_with_pipes
GLib.my_spawn = core.callable.new {
    name = 'GLib.my_spawn',
    addr = core.gi.GLib.resolve.g_spawn_async_with_pipes,
    ret  = api.return_type,
    api.args[1].typeinfo,
    api.args[2].typeinfo,
    api.args[3].typeinfo,
    api.args[4].typeinfo,
    api.args[5].typeinfo,
    api.args[6].typeinfo,
    -- Handle the last 4 arguments as plain pointers
    ti.ptr,
    ti.ptr,
    ti.ptr,
    ti.ptr
}

local flags = GLib.SpawnFlags { 'SEARCH_PATH', 'CLOEXEC_PIPES' }
local args = { 'echo', 'hello, world' }
print(GLib.spawn_async_with_pipes(nil, args, nil, flags))
print(GLib.my_spawn(nil, args, nil, flags))
Rio6 commented 2 years ago

Is this a bug for GLib then? I'm not familiar with GI to know whether the value of output parameters should change the functions behaviour. Would it make sense to have like a lgi.NULL_OUTPUT variable that when used in out parameter, it makes it NULL instead of allocating a new one?

ntd commented 2 years ago

Il giorno dom, 01/05/2022 alle 14.56 -0700, Rio6 ha scritto:

Is this a bug for GLib then?

Probably I've not been clear enough: the point of all the above is there is no bug. It is the result of a couple of design decisions that makes what you are requesting impossible to achieve.

I'm not familiar with GI to know whether the value of output parameters should change the functions behaviour. Would it make sense to have like a lgi.NULL_OUTPUT variable that when used in out parameter, it makes it NULL instead of allocating a new one?

That option would break basically any non-trivial LGI code.

What's wrong with the workaround I provided? I find it a perfectly viable solution for an unusual problem. And this is quite unusual: it is the first time I met an API that changes behavior depending on an output parameter.

Ciao. -- Nicola

ntd commented 2 years ago

I just opened a merge request in GNOME to address this issue.

Let's see how things evolve...

Rio6 commented 2 years ago

Right, if this is an unusual occurance then creating an one-off custom function would make more sense. Thanks for answering.

ntd commented 2 years ago

Link to the same issue on the Python bindings.

ntd commented 2 years ago

@Rio6: the above merge request will be included in glib-2.74. This means you will be able to do what you requested by leveraging the newly introduced spawn flags (G_SPAWN_STDIN_FROM_DEV_NULL, G_SPAWN_CHILD_INHERITS_STDOUT and G_SPAWN_CHILD_INHERITS_STDERR):

local lgi  = require 'lgi'
local GLib = lgi.require 'GLib'

local cwd   = nil
local argv  = { 'echo', 'hello, world' }
local envp  = nil
local flags = GLib.SpawnFlags {
    'SEARCH_PATH',
    'CLOEXEC_PIPES',
    'STDIN_FROM_DEV_NULL',
    'CHILD_INHERITS_STDOUT',
    'CHILD_INHERITS_STDERR,
}
print(GLib.spawn_async_with_pipes(cwd, argv, envp, flags))
Rio6 commented 2 years ago

That's great, thanks for your work.