Start and stop a program

Hello Vala community,

i want to start and stop a program by toggling a button.

for a simple test, this tries to start/stop another instance of this:

// wat.vala
// valac --pkg gtk4 wat.vala
Subprocess proc;

int main (string[] argv) {
  var app = new Gtk.Application(
    "com.example.GtkApplication",
    ApplicationFlags.DEFAULT_FLAGS
  );

  app.activate.connect(() => {
    var window = new Gtk.ApplicationWindow(app);
    var button = new Gtk.ToggleButton.with_label("start");
    string[] cmd = {"./wat"};

    button.toggled.connect(() => {
      var down = button.get_active();
      print(@"click $down\n");
      if (down) {
        try {
          proc = new Subprocess.newv(cmd, SubprocessFlags.NONE);
          var pid = proc.get_identifier();
          print(@"PID ON START: $pid\n");
        } catch (Error e) {
          print(@"ERROR: $(e.message)\n");
        }
        button.label = "stop";
      } else {
        var pid = proc.get_identifier();
        if (pid == null) {
          print("PID LATER: null\n");
        } else {
          print(@"PID LATER: $pid\n");
        }
        proc.force_exit();
        button.label = "start";
      }
    });

    window.set_child(button);
    window.present();
  });

  return app.run(argv);
}

but it does not stop the second instance :frowning:

  1. click on “start”
    • another instance of wat is started
    • but why does the “PID ON START” not exist when i look in ps aux?
  2. click on “stop”
    • the other instance of wat is not stopped
    • why is the “PID LATER” null?

what am i doing wrong?

Cheers
Osku

PS. Vala 0.56.18 on Arch Linux

The wat program has already exited by the time you call

  • ps aux
  • proc.get_identifier()

You can confirm with following command (e.g. giving sleep time 1 and 100 seconds)

   string[] cmd = { "/usr/bin/sleep", "100"};

Aaah, interesting!

It works for sleep 100, but why doesn’t it work for above program?

How can i make above code work when starting it’s compiled program?

From what I see no new process is created, only a new thread within the parent process is created, which I guess is due to the Application ID being the same for the 2 instances, making GIO to launch a thread than a process.

Before clicking Start button.

bash(11567)---test(28582)-+-{test}(28583)
                          |-{test}(28584)
                          |-{test}(28585)
                          |-{test}(28586)
                          |-{test}(28588)
                          |-{test}(28589)
                          |-{test}(28590)
                          `-{test}(28591)

After clicking Start button.

bash(11567)---test(28582)-+-{test}(28583)
                          |-{test}(28584)
                          |-{test}(28585)
                          |-{test}(28586)
                          |-{test}(28589)
                          |-{test}(28590)
                          |-{test}(28591)
                          `-{test}(28641)

What exactly are you trying to achieve here?

My current use-case would be to run make foo, that runs ls piped to entr and then valac … etc, for a simple “build&run on source change” thing :crazy_face: (And then parse the output to show in UI, just for fun and learning)

But i guess what i really want is, start any program (even bash -c "...") and be able to (parse output and) stop or kill it with a button press.

Sorry, i’m new to Vala and all this system? programming, so i don’t even know if GLib.Subprocess is the right thing for this, and what is even required for “start and stop any program” :sweat_smile:

Refer https://docs.gtk.org/glib/spawn.html for more details.

Thank you for the tip!

Gtk.Application or Gtk.ApplicationWindow indeed seems to do some magic when spawned from the same program. When using only a Gtk.Window i can now easily stop wat spawned from wat. (and is now also shown in ps aux)

So the first problem is solved :partying_face:

But spawning a bash script, bash -c, make, or entr apparently does some other magic that i just cannot stop as simple as ctrl+c in bash terminal.

I don’t understand much, but just found these:

And i only got this to work: pstree -A -p $pid | grep -Eow '[0-9]+' | xargs kill :woozy_face:

So here’s my stupid? newbie test that seems to work for my bash/make use-case :sweat_smile: :

# makefile

.PHONY: build hot

build:
  valac  -X -w --pkg gtk4 grop.vala

hot:
  ls *.vala | entr -rcs 'make build && ./grop'
// grop.vala

public class Thing : Gtk.Window
{
  Pid pid;
  int stdout;
  int stderr;
  Gtk.TextView text_view;

  public Thing (string cmdline)
  {
    var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
    var print_btn = new Gtk.Button.with_label("print");
    var spawn_tgl = new Gtk.ToggleButton.with_label("start");
    text_view = new Gtk.TextView();
    text_view.hexpand = true;
    text_view.vexpand = true;
    text_view.monospace = true;
    default_width = 640;
    default_height = 480;
    print_btn.clicked.connect(()=>{
      print("hello world\n");
    });
    spawn_tgl.toggled.connect(()=>{
      bool run = spawn_tgl.get_active();
      if (run) {
        spawn_tgl.label = "stop";
        print("START -------------\n");
        spawn_cmdline(cmdline);
      } else {
        spawn_tgl.label = "start";
        print("STOP --------------\n");
        kill_cmdline();
      }
    });
    box.append(print_btn);
    box.append(spawn_tgl);
    box.append(text_view);
    set_child(box);
  }

  private bool process_line (IOChannel channel, IOCondition condition, string stream_name)
  {
    if (condition == IOCondition.HUP) {
      return false;
    }
    try {
      string line;
      channel.read_line (out line, null, null);
      //print ("%s: %s", stream_name, line);
      text_view.buffer.text += @"$stream_name: $line";
    } catch (IOChannelError e) {
      print ("%s: IOChannelError: %s\n", stream_name, e.message);
      return false;
    } catch (ConvertError e) {
      print ("%s: ConvertError: %s\n", stream_name, e.message);
      return false;
    }
      return true;
  }

  private void kill_cmdline ()
  {
    string[] cmd = {"bash", "-c",
      @"pstree -A -p $pid | grep -Eow '[0-9]+' | xargs kill"};
    string[] env = Environ.get();
    print(@"Kill pid tree: $pid\n");
    try {
      Process.spawn_sync(
        ".", cmd, env, SpawnFlags.SEARCH_PATH, null, null, null, null
      );
    } catch (SpawnError e) {
      print(@"ERROR killing pid $pid: $(e.message)\n");
    }
  }

  private void spawn_cmdline (string cmdline)
  {
    string[] cmd = {"bash", "-c", cmdline};
    string[] env = Environ.get();
    try {
      Process.spawn_async_with_pipes(
        ".", cmd, env, SpawnFlags.SEARCH_PATH,
        null,
        out pid,
        out stdout,
        out stderr
      );
      print(@"Pid on start: $pid\n");
      IOChannel output = new IOChannel.unix_new(stdout);
      output.add_watch(IOCondition.IN | IOCondition.HUP, (channel, condition) => {
        return process_line (channel, condition, "stdout");
      });
      IOChannel error = new IOChannel.unix_new(stderr);
      error.add_watch(IOCondition.IN | IOCondition.HUP, (channel, condition) => {
        return process_line (channel, condition, "stderr");
      });
    } catch (SpawnError e) {
      print(@"ERROR: $(e.message)\n");
    }
  }
}

int main (string[] args) {
  Gtk.init();
  //var thing = new Thing("./grop");
  var thing = new Thing("make hot");
  thing.present();

  while (Gtk.Window.get_toplevels ().get_n_items () > 0) {
    MainContext.@default ().iteration (true);
  }
  return 0;
}

usage:

  1. build and run it: make && ./grop
  2. click “print”
    • should see “hello world” in terminal
  3. toggle “start”
    • should see messages in first window textview
    • should see a new grop window
  4. click “print” in second grop window
    • should see messages in first window textview
  5. click “stop” in first grop window
    • should stop second grop window, and make etc.

Hups, a small “typo” in the code :face_palm:

index e0d3d18..2fb1c74 100644
--- a/grop.vala
+++ b/grop.vala
@@ -6,6 +6,7 @@ public class Thing : Gtk.Window
   int stdout;
   int stderr;
   Gtk.TextView text_view;
+  bool just_for_the_warning;
 
   public Thing (string cmdline)
   {
@@ -83,6 +84,7 @@ public class Thing : Gtk.Window
         ".", cmd, env, SpawnFlags.SEARCH_PATH,
         null,
         out pid,
+        null,
         out stdout,
         out stderr
       );