Chapter 12. Disabling input processing

The introduction explains how an internal execution thread draws the application windows, and processes keyboard and pointer clicks, by invoking various callbacks in response to them. Furthermore, the section called “Callbacks” explains that callbacks should not engage in lengthy activities, but notify the main application execution thread before returning, so that the internal execution thread can proceed with its chores.

Another pointer or keyboard click results in the appropriate callback getting invoked again even if the main application execution thread is still busy with its previous task. The main application thread won't get around to processing the next message from the callback until it's done. This does not result in optimal user experience. The application can also maintain an internal status indication of some kind, that the callbacks can check to see if it's busy with its current task and not take any action.

This chapter gives an example of a different way to stop keyboard and pointer processing. One of the parameters to callbacks is x::w::busy:

#include <x/w/busy.H>

// ...

    button->on_activate
        ([]
         (ONLY IN_THREAD,
          const x::w::callback_trigger_t &trigger,
          const x::w::busy &get_busy)
         {
            // ...

Calling one of x::w::busy's methods returns an opaque mcguffin, a generic reference-counted object, x::ref<x::obj>. Mcguffins are a design pattern from the base LibCXX library. The internal execution thread ignores button and keypress event while the mcguffin object exists, but continues to update and redraw the application window.

No explicit action is needed to resume input processing, which occurs automatically when the mcguffin's object gets destroyed. The sequence of events is as follows:

busy.C gives a general example of this approach:

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

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

#include <x/w/main_window.H>
#include <x/w/button.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/gridfactory.H>
#include <x/w/text_param_literals.H>
#include <x/w/font_literals.H>
#include <x/w/screen.H>
#include <x/w/connection.H>
#include <string>
#include <iostream>
#include <errno.h>
#include <poll.h>
#include <chrono>

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

typedef x::ref<x::obj> mcguffin_t;

typedef x::ptr<x::obj> mcguffinptr_t;

/////////////////////////////////////////////////////////////////////////////
//
// An example of handling user events in a message-based thread to handle
// user UI events.
//
// Use LibCXX library's x::threadmsgdispatcherObj framework. The
// busy.xml file defines two messages for this thread. A stylesheet generates
// an #include file, that we pull in below, that generates skeleton code to
// implement two methods in this object, were_busy() and
// window_close_button_pressed().
//
// These method send a message into the internal message dispatcher queue.
// When processed, the include file provides declarations of
// dispatch_were_busy() and dispatch_close_button_pressed(), which get
// implemented below, to process the messages.

class busythreadObj : public x::threadmsgdispatcherObj {

public:

	busythreadObj();
	~busythreadObj();

	// Invoked from the main program to execute the mainloop() directly.

	void run_directly(const x::w::main_window &main_window);

	// The official entry-point for a new, real, execution thread that
	// uses x::start_threadmsgdispatcher.

	void run(x::ptr<x::obj> &startup_mcguffin);

#include "busy.inc.H"

private:
	// The main message processing loop.

	void mainloop(msgqueue_auto &);

	// The "window_close_button" message clears this flag, and mainloop()
	// stops.

	bool is_running;

	// The mcguffin sent via the "were_busy" message gets stored here,
	// and after five seconds this object gets removed. This will be
	// the last reference to the mcguffin, and its destruction then
	// re-enables keyboard and mouse processing.
	//
	// Note that this is a pointer to an object inside run(). This is
	// not strictly necessary, but is extra "insurance". This can be an
	// ordinary class member, but if run() returns for some reason, the
	// mcguffin will still remain in the class instance, and if the
	// main application thread still has its own reference to this
	// busythread instance the mcguffin does not get destroyed.
	//
	// This way if run() terminates for some reason, the mcguffin gets
	// automatically destroyed as well. "currently_busy" is accessed only
	// in run().

	mcguffinptr_t *currently_busy;

	// The clock that tells us when the five seconds expire.
	typedef std::chrono::steady_clock busy_clock_t;

	busy_clock_t::time_point busy_until;
};

busythreadObj::busythreadObj()=default;

busythreadObj::~busythreadObj()=default;

// A were_busy message was dispatched.

// Take the mcguffin from the message, and stash it away in our object.
// Note that this can be invoked only from run(), indirectly, as part of
// the event().

void busythreadObj::dispatch_were_busy(const mcguffin_t &mcguffin)
{
	*currently_busy=mcguffin;

	// And start the clock running.
	busy_until=busy_clock_t::now() + std::chrono::seconds(5);
}

// A window_close_button_pressed() message was dispatched.

void busythreadObj::dispatch_window_close_button_pressed()
{
	// Make the mainloop() finish things up.

	is_running=false;
}

// Direct invocation.

void busythreadObj::run_directly(const x::w::main_window &main_window)
{
	msgqueue_auto q{this};

	// Show the main window. This must be done after constructing
	// the msgqueue_auto. For more details see LibCXX base library's
	// documentation. Showing the main application window starts
	// processing of messages from the display server. If this is done
	// before run_directly() gets called, from the main function below,
	// there's a theoretical possibility that one of the callbacks sends
	// a message to this thread before the internal message queue gets
	// initialized, resulting in message loss. No memory leak will result,
	// just the click gets quietly ignored.
	//
	// Showing the main window only after our message queue gets
	// constructed prevents this theoretical race condition from
	// happening.

	main_window->show_all();

	mainloop(q);
}

void busythreadObj::run(x::ptr<x::obj> &startup_mcguffin)
{
	// And when we're using x::start_threadmsgdispatcher, and start a
	// real execution thread, we just construct the msgqueue_auto, and
	// release the startup mcguffin. x::start_threadmsgdispatcher()
	// returns in the main execution thread, which then proceeds to
	// show the main window.

	msgqueue_auto q{this};

	startup_mcguffin=nullptr;

	mainloop(q);
}

void busythreadObj::mainloop(msgqueue_auto &q)
{
	// Note that "is_running" gets updated here, and in
	// dispatch_window_close_button_pressed(). The dispatch function
	// gets called by this execution thread, from event(), so this
	// class member is only accessed by this execution thread (or the
	// "pseudo"-execution start, if we run_directly()), so there are
	// no multithreading-related issues with is_running, and there's no
	// need for any mutexes or condition variables.

	is_running=true;

	mcguffinptr_t currently_busy_instance;

	currently_busy= &currently_busy_instance;

	// Take the threadmsgdispatcher-provided event queue, and put its
	// event file descriptor in non-blocking mode, so we can handle
	// poll()ing in order to support the busy mcguffin's timer
	// expiration.
	auto eventfd=q->get_eventfd();

	struct pollfd pfd[1];

	pfd[0].fd=eventfd->get_fd();

	eventfd->nonblock(true);

	while (is_running)
	{
		// If a message was received, dispatch the message.

		if (!q->empty())
		{
			try {
				q.event();
			} catch (const x::exception &e)
			{
				// Report any exceptions.

				e->caught();
			}
			continue;
		}

		// poll() either until the event file descriptor fires
		// (indicating a new message in threadmsgdispatcher's
		// message queue), or until the busy mcguffin expires,
		// if we have one.

		int expiration= -1;

		if (*currently_busy)
		{
			auto now=busy_clock_t::now();

			if (now >= busy_until)
			{
				// busy mcguffin's timer has expired. Drop our
				// ref on the mcguffin object. Mcguffin's
				// destruction re-enables input processing.
				*currently_busy=nullptr;
			}
			else
			{
				// compute when the mcguffin expires, so we
				// go back here when that happens.
				expiration=std::chrono::duration_cast
					<std::chrono::milliseconds>
					(busy_until-now).count();

				if (expiration == 0) // Edge case, CPU spikes.
					expiration=1;
			}
		}

		pfd[0].events=POLLIN;

		if (poll(pfd, 1, expiration) < 0)
			std::cerr << "poll: " << strerror(errno) << std::endl;
	}
}

// Main window's creator, factored out for readability.

static inline void create_main_window(const x::w::main_window &main_window,
				      const x::ref<busythreadObj> &mythread)
{
	auto layout=main_window->gridlayout();
	x::w::gridfactory factory=layout->append_row();

	// Create two buttons. Install a callback that executes when
	// the button gets selected.
	//
	// Button callbacks received two parameters. Of interest is the
	// second one, x::w::busy. Calling its methods creates and returns
	// a "mcguffin". An opaque reference-counted handle for an object,
	// x::ref<x::obj>. As long as this object exists, the library does
	// not process any keyboard or pointer clicks, but continues to
	// handle all other display server messages (redraws, resizing,
	// etc...)
	//
	// We take the mcguffin, and send it to our own pseudo-"thread". This
	// keeps the underlying in existence. The thread takes the handle
	// stashes it away, then five seconds later gets rid of it, which
	// reenables input processing.

	factory->create_button
		({"Shade"})->on_activate
		([mythread]
		 (ONLY IN_THREAD,
		  const x::w::callback_trigger_t &ignore,
		  const x::w::busy &get_busy) {
			mythread->were_busy(get_busy.get_shade_busy_mcguffin());
		});

	factory=layout->append_row();

	factory->create_button
		({"Pointer"})->on_activate
		([mythread]
		 (ONLY IN_THREAD,
		  const x::w::callback_trigger_t &ignore,
		  const x::w::busy &get_busy) {
			mythread->were_busy(get_busy.get_wait_busy_mcguffin());
		});
}

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

	// Create a mythread object.

	auto mythread=x::ref<busythreadObj>::create();

	auto main_window=x::w::main_window
		::create([&]
			 (const auto &main_window)
			 {
				 create_main_window(main_window, mythread);
			 });

	main_window->set_window_title("Very busy!");

	guard(main_window->connection_mcguffin());

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

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

	// We use LibCXX base library's threadmsgdispatcher framework to
	// base our mythreadObj on. Typically one uses
	// x::start_threadmsgdispatcher() to start a new execution thread to
	// run() the object.
	//
	// We can do that, of course, but there's no real need, in this
	// simple example, so we can run_directly().

#if 1
	mythread->run_directly(main_window);

#else

	// But if we insist on doing it the long way, we'll start the
	// execution thread.

	auto thread=x::start_threadmsgdispatcher(mythread);

	// Now that the execution thread was started, we can show() the
	// main window.

	main_window->show_all();

	// And just wait for the thread to terminate.

	thread->wait();
#endif
}

int main(int argc, char **argv)
{
	try {
		busy();
	} catch (const x::exception &e)
	{
		e->caught();
		exit(1);
	}
	return 0;
}

busy.C uses several classes, templates, and tools from the base LibCXX library. x::threadmsgdispatcherObj is a design pattern for a message-oriented execution thread, with a stylesheet that uses a simple XML file that defines messages, and generates some skeleton code to implement each message as one class method that sends a message to the execution thread, and a second method that the execution thread runs, the dispatch method, that receives it. The Makefile in the examples directory runs the stylesheet to create busy.inc.H that gets pulled into the class definition.

busy.C creates a window with two buttons. Pressing either of the two buttons stops the window from reacting to any additional button clicks, or keys (the only keys that do anything here are the tab keys that manually switch the input focus between the buttons, but this is sufficient for demonstration purposes) for five seconds.

One of the two buttons' callbacks uses get_shade_busy_mcguffin(), and the other one uses get_wait_busy_mcguffin():

    [mythread]
    (ONLY IN_THREAD,
     const x::w::callback_trigger_t &ignore,
     const x::w::busy &get_busy) {
         mythread->were_busy(get_busy.get_wait_busy_mcguffin());
    }

The callbacks call one of these two methods, and call the were_busy() from the thread dispatcher object, which send a message to the object's thread, that includes the acquired mcguffin as part of the message, then returns to the library's execution thread. Both get_shade_busy_mcguffin() and get_wait_busy_mcguffin() produce mcguffins that block button and key press event processing during their existence. get_shade_busy_mcguffin() draws a theme-specified shade over the entire application window to make it look washed out and indicate that it's inactive, and get_wait_busy_mcguffin() changes the window's pointer to a please wait shape. This provides visual indication that the main application is running.

The execution thread object receives and dispatches the message, and arranges for the mcguffin to exist for five seconds before destroying it, at which point the window's appearance gets restored to normal.