Chapter 44. Singleton windows

Some application do not support, or prefer not to have, multiple windows. Executing another copy of the application does not create a new window, by a new process. A common result of that is that the existing window gets moved to be above all other window, or some other action takes place.

singleton.C gives a basic design pattern for this kind of an application. singleton.C opens a single window. Running it again does not create a new window, but moves the existing window on top of all other windows on the display screen. Running it again with a stop parameter closes the currently running window and terminates its process:

/*
** Copyright 2018-2021 Double Precision, Inc.
** See COPYING for distribution information.
*/

#include "config.h"
#include <x/mpobj.H>
#include <x/exception.H>
#include <x/ref.H>
#include <x/obj.H>
#include <x/appid.H>
#include <x/singletonptr.H>
#include <x/managedsingletonapp.H>

#include <x/w/main_window.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/gridfactory.H>
#include <x/w/label.H>
#include <x/w/screen.H>
#include <x/w/connection.H>
#include <string>
#include <iostream>
#include <cstdlib>

#include "close_flag.H"

std::string x::appid() noexcept
{
	return "singleton.examples.w.libcxx.com";
}

// Use a singletonptr to store our main_window and close_flag, so that they
// can be instantiated in automatic scope, yet conveniently accessed from
// anywhere.

class singleton_appObj : virtual public x::obj {

public:

	const x::w::main_window main_window;

	const close_flag_ref close_flag;

	singleton_appObj(const x::w::main_window &main_window,
			 const close_flag_ref &close_flag)
		: main_window{main_window},
		  close_flag{close_flag}
	{
	}
};

typedef x::ref<singleton_appObj> singleton_app;

typedef x::singletonptr<singleton_appObj> singleton_app_s;

void singleton()
{
	x::destroy_callback::base::guard guard;

	auto close_flag=close_flag_ref::create();

	auto main_window=x::w::main_window::create
		([&]
		 (const auto &main_window)
		 {
			 auto glm=main_window->gridlayout();

			 x::w::gridfactory f=glm->append_row();

			 f->padding(10.0);
			 f->create_label("I am a singleton!");
		 });

	main_window->set_window_title("Singleton!");

	guard(main_window->connection_mcguffin());

	main_window->on_disconnect([]
				   {
					   _exit(1);
				   });

	main_window->on_delete
		([close_flag]
		 (ONLY IN_THREAD,
		  const auto &ignore)
		 {
			 close_flag->close();
		 });

	main_window->show_all();

	singleton_app_s singleton{ singleton_app::create(main_window,
							 close_flag) };

	x::mpcobj<bool>::lock lock{close_flag->flag};

	lock.wait([&] { return *lock; });

}

/////////////////////////////////////////////////////////////////////////////

// A message to the singleton when another instance gets started. We'll just
// pass the new instance's argv.

class app_argsObj : virtual public x::obj {

public:

	std::vector<std::string> args;

	app_argsObj()=default;

	app_argsObj(int argc, char **argv)
		: args{argv, argv+argc}
	{
	}

	template<typename ptr_type, typename iter_type>
	static void serialize(ptr_type ptr, iter_type &iter)
	{
		iter(ptr->args);
	}
};

typedef x::ref<app_argsObj> app_args;

// The return value message. For demo purposes, it's just one string.

class app_retObj : virtual public x::obj {

public:

	std::string message;

	app_retObj()=default;

	app_retObj(const std::string &message) : message{message}
	{
	}

	template<typename ptr_type, typename iter_type>
	static void serialize(ptr_type ptr, iter_type &iter)
	{
		iter(ptr->message);
	}
};

typedef x::ref<app_retObj> app_ret;


// And our managed application singleton object.

class app_instanceObj : virtual public x::obj {

public:

	// First time the app runs, for real.

	// We also get the initial set of argument, packaged into app_args.
	// We'll ignore that.

	app_ret run(uid_t uid,
		    const app_args &ignore)
	{
		singleton();

		return app_ret::create("Done!");
	}

	// Another instance of the app has started.

	// If the additional instance command-line parameter is "stop" we'll
	// hara-kiri ourselves. Otherwise we'll just raise our window.
	void instance(uid_t uid,
		      const app_args &args,
		      const app_ret &ret,
		      const x::singletonapp::processed &flag,
		      const x::ref<x::obj> &mcguffin)
	{
		flag->processed();

		singleton_app_s singleton;

		if (!singleton)
			return;

		if (args->args.size() > 1 &&
		    args->args.at(1) == "stop")
		{
			singleton->close_flag->close();

			ret->message="Closing...";
		}
		else
		{
			singleton->main_window->raise();

			ret->message="Window raised...";
		}
	}

	// SIGINT, or something. Orderly shutdown.

	void stop()
	{
		singleton_app_s singleton;

		if (singleton)
			singleton->close_flag->close();
	}
};

typedef x::ref<app_instanceObj> app_instance;

int main(int argc, char **argv)
{
	try {
		// For debugging purposes, avoid bootstrapping the entire
		// singleton infrastructure, and jump directly into the code.

		if (getenv("DEBUG"))
		{
			singleton();
		}
		else
		{
			auto [ret, ignore]=x::singletonapp::managed
				([]
				 {
					 return app_instance::create();
				 },
				 app_args::create(argc, argv));

			std::cout << ret->message << std::endl;
		}
	} catch (const x::exception &e)
	{
		e->caught();
		exit(1);
	}
	return 0;
}

Most of this functionality comes from the base LibCXX library. This example uses singleton object pointers that were mentioned in Chapter 30, Print Dialog together with LibCXX's singleton application instance classes to implement this functionality.