GIO Application Commandline Does Trigger 'activate' Signal

I’m working on a demo of gio.ApplicationCommandLine for my blog and ran into a snag…

The generic demo that simply opens a window works as expected. The activate signal fires, the window opens, etc.

But when I add the command-line signal, which also works as expected, the activate signal no longer fires.

Is this how it’s suppose to work? Either an application is a windowed app or it can process the command line args using gio.ApplicationCommandLine, but not both?

Here’s the code:

import gio.Application : GioApplication = Application;
import gtk.Application : GtkApplication = Application;
import gtk.ApplicationWindow;
import gio.ApplicationCommandLine;

import std.stdio;

void main(string[] args)
{

	MyApplication thisApp = new MyApplication(args);
	
} // main()


class MyApplication : GtkApplication
{
	ApplicationFlags flags = ApplicationFlags.HANDLES_COMMAND_LINE | ApplicationFlags.HANDLES_OPEN;
//	GApplicationFlags flags = GApplicationFlags.FLAGS_NONE;
	string id = "com.gtkdcoding.app.app_020_01_barebones"; // rules

	this(string[] args)
	{
		super(id, flags);
		
		addOnActivate(&onActivate);
		addOnCommandLine(&onCommandLine);
		addOnOpen(&onOpen);
		
		run(args);
		
	} // this()


	void onOpen(void* entity, int number, string text, GioApplication app)
	{
		writeln("triggered onOpen...");
		
	} // onOpen()
	

	void onActivate(GioApplication app) // non-advanced syntax
	{
		writeln("triggered onActivate...");
        AppWindow appWindow = new AppWindow(this);
		
    } // onActivate()


	int onCommandLine(ApplicationCommandLine acl, GioApplication app) // non-advanced syntax
	{
		int exitStatus = true;
		string[] args = acl.getArguments();
		
		writeln("triggered onCommandLine...");
		
		writeln("\tcommandline args: ", args);
		
		return(exitStatus);
		
    } // onActivate()

} // class MyApplication


class AppWindow : ApplicationWindow
{
	int width = 400, height = 200;
	string title = "Barebones Application";
	
	this(MyApplication myApp)
	{
		super(myApp);
		setSizeRequest(width, height);
		setTitle(title);
		showAll();
		
	} // this()
	
} // class AppWindow

Nice that you are progressing to the interesting topics now…

The GIO Application signals are not really easy, I read about it some years ago, but can not really remember.

There are at least 3 pages available:

https://python-gtk-3-tutorial.readthedocs.io/en/latest/application.html

https://developer.gnome.org/GtkApplication/

https://wiki.gnome.org/HowDoI/GtkApplication/CommandLine

One has a C example, I think we should use and extend that for testing. I can do it tomorow.

Here is a test in C:

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

#include <gtk/gtk.h>
#include <stdio.h>
static void
activate (GApplication *app,
          gpointer      user_data)
{
  GtkWidget *widget;

  widget = gtk_application_window_new (GTK_APPLICATION (app));
  gtk_widget_show (widget);
  printf("activate\n");
}

static int
comm (GApplication *app,
          GApplicationCommandLine *comm,
          gpointer      user_data)
{
  printf("comm\n");
}

int
main (int argc, char **argv)
{
  GtkApplication *app;
  int status;

  app = gtk_application_new ("org.gnome.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  g_signal_connect (app, "command-line", G_CALLBACK (comm), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

Compiles and opens the window, but seems that comm() function is never executed. Have to read docs…

LOL. Hi, Stefan. “Interesting?” What’s that supposed to mean, eh? :slight_smile:

And, dang it, I should have mentioned right up front that I’m doing this on Windows where some things are (I think) still not exactly up to snuff. I did read somewhere that the Windows implementation of Gio.ApplicationCommandLine isn’t working, but it does. But then, maybe that poster was referring to the problem I’ve run into here.

Yup, I’ve been digging through those pages (and similar hits) off and on all day. Either—as I mentioned above—it’s just not a 100% implementation on Windows or I’m having more trouble than usual translating from C. (sigh)

Nothing’s jumping out at me that seems like it would affect this, no extra flags or signal hook-ups seem to be explicitly needed. But it could be something that’s NOT needed in C, but IS needed in D… because of the wrapper. I don’t know. I’m jumping at ghosts now.

Can’t wait to see what you may come up with, Stefan. Later, dude.

Yup, I’m getting either/or, but not both. Odd stuff, this.

It occurred to me that this might have something to do with processing a chain of signals. Like, if you have a series of bool-return callbacks hooked up and you have to make sure to return ‘false’ for each so the either chain is triggered. But, the onCommandLine callback returns an int. I haven’t yet figured out the significance of that return value. I’ve tried assigning a couple of different values (the obvious ones: 1, 0, -1) but nothing changes.

There is much to read – have to watch TV news now…

https://developer.gnome.org/gio/stable/GApplication.html#GApplication-command-line

https://developer.gnome.org/gio/stable/GApplication.html#g-application-run

https://developer.gnome.org/gio/stable/GApplicationCommandLine.html

I have a solution, although it’s rather unorthodox. I’m probably breaking all kinds of rules here, but at least the window opens and the pseudo-handling of the arg list happens.

In the commandline callback, I just threw in a call to activate().

int onCommandLine(ApplicationCommandLine acl, GioApplication app) // non-advanced syntax
	{
		int exitStatus = true;
		string[] args = acl.getArguments();
		activate(); // ****************************
		writeln("triggered onCommandLine...");
		
		writeln("\tcommandline args: ", args);

		return(exitStatus);
		
    } // onActivate()

Seems too hacky.

I also found this: https://wiki.gnome.org/HowDoI/GtkApplication/CommandLine which makes some interesting implications, but in the wrong direction.

My understanding is GApplication::activate will not be emitted in this case. For reference, this section should clarify that somewhat. You can also simply search the source there for g_application_activate to see when this vfunc/signal will be emitted.

However, I do believe you should be passing G_APPLICATION_HANDLES_COMMAND_LINE in the flags to get GApplication::command-line.

Well, I watched your D GTK blog in the past, and for a long time the content was what most of us already know from GTK2 and the Krause book. With the GApplication stuff you have arrived at the more difficult parts of GTK3/4

I recently discovered that GTK D is now official supported and that GTK4 bindings seems to be available, see https://www.gtk.org/docs/language-bindings/ That is nice.

I also found this: https://wiki.gnome.org/HowDoI/GtkApplication/CommandLine

Thanks for that link, I have seen that content before, but forget about it yesterday.

I think the GApplication startup signal handling is one of the most difficult parts of GTK3/4 unfortunately, and it may interfere with the command line handling of our host languages. Nim has advanced handling of command line parameters already, and I assume D and Rust too.

When I started writing the long Readme for the Nim bindings some years ago, I initially had the feeling that I should try to write something about the GApplication startup process, but I never managed to do it. It would be at least ten pages I guess, and maybe such a long description would even scare off Nim GTK users. One of the terms I do remember about GApplication is “remote app” – no idea what this really is, I think I should read all of the above links…

Ain’t that the truth! :slight_smile: Still, I was just kidding around with that mock indignation stuff.

AFAIK, the GTK4 bindings for D are still some way off, but I’m not involved at that level, so I can’t say for sure. Last I spoke with Mike Wey about it, we’re lagging behind, even in the GTK3 stuff. The latest version covered is 3.24.x or thereabouts. The wrapper version is 3.9x, which may be misleading.

Being mostly interested in the GUI side, I haven’t really dug into that, so I couldn’t tell you.

It should be similar to handling multiple GDK signals, shouldn’t it? At least, that would make sense to me. Why reinvent the wheel? But what’s puzzling to me is that 1) the only callback method returning a non-void value is onCommandLine(), and 2) there’s no indication (in what I’ve read so far) as to what onCommandLine()'s return value means or what it’s used for. It must mean something or why bother returning an int?

Perhaps it’s ‘remote’ as in VDI. That’s the only thing that comes to mind.

That seems to be the case, but I have to wonder why it’s designed like this. If a file-name is supplied on the command line on start-up, I would expect the file to be opened in the app. It shouldn’t matter if the app has a GUI or not, so a GUI app should open its window under these circumstances.

Thanks for the link, Andy. This seems to imply that I have to designate the app as ‘local’ before a window opens. Huh. Must try that.

I thought I was doing that with this line:

ApplicationFlags flags = ApplicationFlags.HANDLES_COMMAND_LINE | ApplicationFlags.HANDLES_OPEN;

The hint of andyholmes was indeed important, we need the G_APPLICATION_HANDLES_COMMAND_LINE flag of course.

And I think your solution calling g_application_activate (app) in the command-line callback makes indeed sense. Because depending on command line parameters maybe we do not want to open GUI windows at all. Unfortunately I have not found API docs confirming this assumption yet.

For the GApplication explanations, I think we should split it in too parts. One simplified early in our books, so that beginners can use app style tools from the beginning and don’t have to use the old GTK2 style with init_gtk(), and an extended explanation in a later section. At least that would be the plain for Nim.

Do you have already an idea about the int return value of the command-line callback? For what is that return value used?

#include <gtk/gtk.h>
#include <stdio.h>
static void
activate (GApplication *app,
          gpointer      user_data)
{
  GtkWidget *widget;

  widget = gtk_application_window_new (GTK_APPLICATION (app));
  gtk_widget_show (widget);
  printf("activate\n");
}

static int
comm (GApplication *app,
          GApplicationCommandLine *comm,
          gpointer      user_data)
{
  printf("comm\n");
  g_application_activate (app);
}

int
main (int argc, char **argv)
{
  GtkApplication *app;
  int status;

  app = gtk_application_new ("org.gnome.example", G_APPLICATION_HANDLES_COMMAND_LINE);
  g_signal_connect (app, "command-line", G_CALLBACK (comm), NULL);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

What I have still to learn is what is the difference between files passed to the app, versus file-names passed on the command line.

From what I’ve read so far, it’s just success (0) or continue (-1), similar to bool return values in the GDK signal handling system which makes me wonder why it wasn’t set up the same way… unless it’s got something to do with local/remote or primary/local instantiation of the app.

I’m going to give this a little more time, but if I don’t find an answer soon, I’m just going to put it on the back burner and move on. It’s peripheral to my main cause anyway.

The blog is rather narrow in scope, aiming to get people up and running with GUI programming and isn’t intended as a comprehensive guide to GTK/GIO/etc.

I don’t think calling activate() manually yourself is a bad idea per se. My understanding of the process goes something like this:

  1. ::handle-local-options

    This means “local to the calling process”, or the process the user just typed into a terminal. Here you might catch a --version arg and just print and exit.

  2. g_application_register()

    Since GApplication uses DBus names to ensure single-instance behaviour, this is where the application discovers whether it is the “primary instance” or a “remote instance”. The “primary” instance is the one that owns the DBus name (eg. org.gnome.Software).

  3. Next up is the if-then chain

    • G_APPLICATION_IS_SERVICE

      Here is where a background service would start running.

    • G_APPLICATION_HANDLES_COMMAND_LINE

      Unlike ::handle-local-options (and the more powerful ::local-command-line), this ::command-line signal is emitted in the “primary” instance. This is how you get command line arguments from a secondary (aka “remote”) terminal invocation to an already running GApplication (aka “primary”).

    • If no command line routes are taken, another split between two options may happen:

      • ::open, the signal for G_APPLICATION_HANDLES_OPEN

        This is also emitted in the “primary” instance. The reason this doesn’t result in the ::activate signal being emitted by default, is so you can select a different UI response for files (if you want) such as a solo GtkFileChooserDialog or in the case of a text editor you might open a second editor window.

      • Failing anything else being requested, ::activate is emitted in the “primary” instance.

        Usually this results in the main window of an application opening or being presented. For context, this is also the signal emitted when a notification banner is clicked (by default).

Hope that helps.

Gah! I think my brain just melted. :crazy_face:

That was a lot of information, Andy.

What I’m trying to do is find my way through the jungle to answer one simple question: How to handle command line arguments and open a window at the same time.

Thanks.

Okay :slight_smile:

Just pass G_APPLICATION_HANDLES_COMMAND_LINE and from the ::command-line callback handle your options and open your window. You can just call g_application_activate() if that function is setup to open your main window; there’s nothing wrong with that.

By contrast, just imagine a command line option called --hide-window. Clearly this option would have to be passed to the “primary” instance, but obviously shouldn’t result in the window opening.

Okay, thanks, Andy. I did do that, but thought I might be violating the spirit of GIO signals. It does seem strange that I have to deliberately call activate() under these circumstances when I don’t if I’m not handling the command-line signal. It seems like a cheat or a cludge, but as long as it works, I’m moving on.

Again, thanks.

Got it sorted out…

What matters is the order in which you hook up the callback signals. The void-return callbacks are hooked up first. Anything that returns an int needs to be hooked up last. Then I don’t have to make an activate() call. Everything works in a sane way similar to the GDK system of signals and callbacks where callbacks returning a bool can interrupt the chain of signals being processed by returning true, all others returning false (meaning: we ain’t done yet).

Here’s the order of signals I used:

addOnActivate(&onActivate);
addOnOpen(&onOpen);
addOnStartup(&onStartup);
addOnShutdown(&onShutdown);
addOnHandleLocalOptions(&onHandleLocalOptions);
addOnCommandLine(&onCommandLine);

Thanks everyone for playing along.

1 Like

Thanks for testing. I had the same idea, but was to lazy to test, and indeed could not imagine that order really makes a difference. And I still find it strange.

Well, if you compare it to GDK signals/callbacks, there is logic to it, but you have to think in terms of signal chains rather than individual signals. With GDK, returning true from a single signal in a chain will halt signal processing for the entire chain. As long as false is being returned by any particular signal, the chain continues. In GIO, void takes the place of false.

I’m not 100% sure of this yet, but it seems like a theory worth testing. And this morning, I’ll be experimenting with multiple int callbacks to see if the signal chain can be halted by changing what’s returned.