Vala Run comand line async

Hi, I’m newbe in Vala. I want to make a simple GUI for dnf package manager. What is the best way to do $dnf update asynchronously so that the interface does not hang and still get a response. Something easier to use than spawn_async_with_pipes. I think GTask would fit, but on valadoc example of its use remained on C. May be other simple ways.

GSubprocess is the modern GIO API. You could also use libvte to embed a terminal.

Although, If I were writing a frontend to dnf, I’d probably do it in python so that I could use the dnf API.

There’s also PackageKit, which has a GObject API that can be used from Vala, but its longterm future is apparently uncertain.

Ty for fast answer. I don’t really like Python. And I’m not going to do directly something serious, just want to practice, for this I have enough of the usual spawn console commands and return their output. Where can I find examples of using this Subprocess? The first that I found this such same question without answer on Reddit.

This is some test code using a subprocess to call gnuplot to do some graphing. It is in C but maybe it will help with setting up a subprocess in Vala with dnf.

Eric

//gcc -Wall gnuplot_test.c -o gnuplot_test `pkg-config gtk+-3.0 --cflags --libs`

#include<gtk/gtk.h>
#include<stdio.h>
#include<string.h>

static GRand *rand=NULL;
//Where to write the image file to.
static gchar *test_dir={"/home/eric/Velo/Misc/Pipes/TEST.png"};

static gboolean read_err(GObject *pollable_stream, gpointer user_data);
static void gnuplot_finished(GObject *source_object, GAsyncResult *res, GtkWidget *image);
static void plot_data(GtkWidget *image);

static gboolean read_err(GObject *pollable_stream, gpointer user_data)
  {
    g_print("Read Stream\n");
    char *buffer=g_malloc(500);
    memset(buffer, '\0', 500);
    gssize bytes_read=0;

    bytes_read=g_pollable_input_stream_read_nonblocking((GPollableInputStream*)pollable_stream, buffer, 499, NULL, NULL);
    if(bytes_read>0) g_print("Error From Gnuplot: %s", buffer);
    if(bytes_read==-1) g_print("Input Stream Error.");
    g_input_stream_close((GInputStream*)pollable_stream, NULL, NULL);
    g_free(buffer);

    return FALSE;
  }
static void gnuplot_finished(GObject *source_object, GAsyncResult *res, GtkWidget *image)
  {   
    gtk_image_set_from_file(GTK_IMAGE(image), test_dir);
    gtk_widget_queue_draw(image);
    if(g_subprocess_get_successful(G_SUBPROCESS(source_object))) g_print("Gnuplot Success\n");
    g_object_unref(source_object);
    g_print("Gnuplot Finished\n");
    //Plot the image again.
    plot_data(image);
  }
static void plot_data(GtkWidget *image)
  {
    static gint i=1;
    g_print("\nPlot %i\n", i++);
    gdouble rand_num=g_rand_double(rand);
    gint width=gtk_widget_get_allocated_width(GTK_WIDGET(image));
    gint height=gtk_widget_get_allocated_height(GTK_WIDGET(image));
    
    gchar *cmd=g_strdup("/usr/bin/gnuplot");
    gchar *script=g_strdup_printf("set terminal pngcairo size %d,%d\nset output '%s'\nset xlabel \"sine wave\"\nset yrange [-1:1]\nset ylabel \"amplitude\"\nplot %f*sin(x)", width, height, test_dir, rand_num);
    g_print("%s\n", script);

    //The Gnuplot process.
    GSubprocess *sub_process=g_subprocess_new(G_SUBPROCESS_FLAGS_STDIN_PIPE|G_SUBPROCESS_FLAGS_STDERR_PIPE, NULL, cmd, NULL);
    //Set up the stderr pipe and callback.
    GInputStream *err_stream=g_subprocess_get_stderr_pipe(sub_process);
    GSource *source=g_pollable_input_stream_create_source((GPollableInputStream*)err_stream, NULL);
    g_source_attach(source, NULL);
    g_source_set_callback(source, (GSourceFunc)read_err, NULL, NULL);
    //Set up finished callback.
    g_subprocess_wait_async(sub_process, NULL, (GAsyncReadyCallback)gnuplot_finished, image); 
    //Set up the stdin pipe and send script to gnuplot.
    GOutputStream *stream=g_subprocess_get_stdin_pipe(sub_process);
    //Send the \0 in the string also.
    g_output_stream_write(stream, script, strlen(script)+1, NULL, NULL);
    g_output_stream_flush(stream, NULL, NULL);
    g_output_stream_close(stream, NULL, NULL);    
    
    g_free(cmd);  
    g_free(script);   
  }
int main(int argc, char *argv[])
  {
    gtk_init(&argc, &argv);

    GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Gnuplot GTK");
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    rand=g_rand_new();

    GtkWidget *image=gtk_image_new();
    gtk_widget_set_size_request(image, 400, 400);
    gtk_widget_set_hexpand(image, TRUE);
    gtk_widget_set_vexpand(image, TRUE);

    GtkWidget *view=gtk_viewport_new(NULL, NULL);
    gtk_container_add(GTK_CONTAINER(view), image);
    GtkWidget *scroll=gtk_scrolled_window_new(NULL, NULL);
    gtk_container_add(GTK_CONTAINER(scroll), view);

    GtkWidget *grid=gtk_grid_new();
    gtk_grid_attach(GTK_GRID(grid), scroll, 0, 0, 1, 1);
    
    gtk_container_add(GTK_CONTAINER(window), grid);

    gtk_widget_show_all(window);

    //Loop the plotting. When one plot is done and opened in the image widget, start another plot.
    plot_data(image);

    gtk_main();

    g_rand_free(rand);

    return 0;
  }
1 Like

@chrisaw @cecashon Ty for answers. I found this but I get an error associated with a lambda function and a warning indicating that implicit .begin is deprecated. But I can’t even see .begin in this code. It seems to me that here in the new versions of Vala something has changed that this lambda has stopped working, but looking at the examples of lambda I can not understand what it is.

console.vala:8.7-8.26: warning: implicit .begin is deprecated
console.vala:8.29-14.7: error: Cannot convert lambda expression to `GLib.Cancellable?'

** (valac:3186): CRITICAL **: 04:47:09.470: vala_method_get_closure: assertion 'self != NULL' failed
console.vala:4.11-4.89: warning: unhandled error `GLib.Error'
      var dnf = new Subprocess (SubprocessFlags.NONE, "dnf", "install", "glib-2.0-devel");
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Compilation failed: 1 error(s), 2 warning(s)

That example was apparently out of date:

void main () {
    var proc = new Subprocess (SubprocessFlags.NONE, "sh", "-c", "sleep 2; true");

    proc.wait_check_async.begin (null, (obj, res) => {
        try {
            proc.wait_check_async.end (res);
            print ("sucessful\n");
        } catch (Error e) {
            print ("error: %s\n", e.message);
        }
    });

    var loop = new MainLoop ();
    loop.run ();
}

wait_check_async.end() actually returns bool, but you can’t really use it from Vala since an error is thrown if it would be false.

See Vala/AsyncSamples for a bit more info on how async methods work in Vala.

1 Like

@chrisaw Ty!, I’m trying to make a simple example, asynchronous output of numbers, and I can not, output 0123456789 0123456789, what is my mistake?

private async void do_stuff () {
    for (int a = 0; a < 10; a++) {
      print(@"$a \t");
    } 
  }
  
  private static int main (string[] args) {
    GLib.MainLoop loop = new GLib.MainLoop ();

    do_stuff.begin ((obj, async_res) => {
        loop.quit ();
      });

    do_stuff.begin ((obj, async_res) => {
    loop.quit ();
    });

    loop.run ();
    return 0;
  }

And another question, Vala has just async, Subprocess, Process, Thread, Lib.Task, I’m finally confused, which of this is outdated and duplicates the functionality?
Here’s an example of what I want to do that looks very simple on Golang.

package main
import "fmt"

func f(n int) {
    for i := 0; i < 10; i++ {
        fmt.Println(n, ":", i)
    }
}

func f2(n int) {
    for i := 0; i < 10; i++ {
        fmt.Println(n, ":", i)
    }
}

func main() {
    go f(0)
    go f2(2)
    var input string
    fmt.Scanln(&input)
}

I had to look this up, but you can yield back to the mainloop:

private async void do_stuff () {
    for (int a = 0; a < 10; a++) {
        print(@"$a \t");
        Idle.add (do_stuff.callback);
        yield;
    }
}

private static int main (string[] args) {
    GLib.MainLoop loop = new GLib.MainLoop ();
    int finished_count = 0;
    do_stuff.begin ((obj, async_res) => {
        finished_count++;
        if (finished_count == 2) {
            loop.quit ();
        }
    });

    do_stuff.begin ((obj, async_res) => {
        finished_count++;
        if (finished_count == 2) {
            loop.quit ();
        }
    });

    loop.run ();
    return 0;
}

This will interleave the two loops. I’m not sure if that’s what you intended.

Everything in glib-2.0.vapi is hand-written. There is, for example, no GProcess in GLib; Process is a namespace invented in the vapi. This was possibly a mistake due to the confusion it causes and the potential for future naming conflicts. As a rule of thumb, generally use the gio api over the one in glib.

async is language syntax that uses GTask internally (it originally used a different now-deprecated API).

A cool example to understand how Idle works. But It looks more like smart goto than coroutine.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.