Problems and Solutions of Nimf

Sat, Jan 27 2024 16:43:23 KST

Updated: Sun Feb 18 07:35:57 KST 2024

There are some issues with the current version of Nimf.
Let’s look at one of the issues.

How to reproduce

1. Log in to the X Window desktop.
2. Confirm that nimf is working: ps aux | grep nimf
3. At this point, switch to the console by pressing Ctrl + Alt + F1.
4. At this time, when running nimf on the console, it cannot receive data from the nimf server.

hodong@:~ $ nimf
nimf 6070 - - INFO: It looks like nimf is already running.
nimf 6070 - - WARNING: nimf.c:158:bool receive_msg(int): Waiting for a reply from the server...
nimf 6070 - - WARNING: nimf.c:158:bool receive_msg(int): Waiting for a reply from the server...
nimf 6070 - - WARNING: nimf.c:158:bool receive_msg(int): Waiting for a reply from the server...

5. Press Alt + F9 again to switch to X Window.
6. Press Ctrl + Alt + F1 again to switch to the console.
7. Afterwards, when running nimf on the console, It can connect to the nimf server and receive data.

Cause Analysis

The nimf server loads various plugins that require Gtk. Gtk is integrated into the main loop of the nimf server. When I first switch to the console by pressing Ctrl + Alt + F1, I confirmed that the nimf server’s main loop stops at gtk_main_iteration_do(FALSE). Therefore, at this point, console applications cannot communicate with nimf in the console environment. Again, If I press Alt + F9 to switch to X Window and Ctrl + Alt + F1 to switch to console, it works fine without stopping.

I don’t know why. Nimf uses CLoop to configure the main loop, but the same problem occurs even if I replace it with GMainLoop or Gtk main loop. Since this problem does not occur in parts where Xlib is used directly, I am assuming that Gtk stops under conditions that I do not know.

Solution

It would be best to create my own GUI toolkit, but it would take years. So I reviewed several GUI toolkits available for both X and Wayland environments.

  • In Qt, hasPendingEvents() has been deprecated, so there seems to be no way to integrate it with a third-party main loop.
  • I am reluctant to use the new GUI toolkit called iced because of bugs.

At the moment, there doesn’t seem to be any GUI toolkit that can replace Gtk. So I’m going to use Gtk, and I’m going to use threads for plugins that use Gtk to solve the problem.

The main thread of the nimf server executes the CLoop main loop. A thread in the Gtk part of the nimf server executes the Gtk main loop. To pass data between threads, I created an event queue like this:

typedef struct _CEvQueue  CEvQueue;
struct _CEvQueue {
  CQueue* q;
  int fd;
  mtx_t mtx;
};

CEvQueue* c_ev_queue_new (CFreeFunc free_func)
{
  CEvQueue* evq;

  int fd = eventfd (0, EFD_CLOEXEC | EFD_SEMAPHORE);
  if (fd == -1)
  {
    c_log_critical ("eventfd failed: %s", strerror (errno));
    return nullptr;
  }

  evq = c_calloc (1, sizeof (CEvQueue));
  mtx_init (&evq->mtx, mtx_plain);
  evq->q = c_queue_new (free_func);
  evq->fd = fd;

  return evq;
}

void c_ev_queue_free (CEvQueue* evq)
{
  close (evq->fd);
  c_queue_free (evq->q);
  mtx_destroy (&evq->mtx);
  free (evq);
}

void c_ev_queue_enq (CEvQueue* evq, void* data)
{
  mtx_lock (&evq->mtx);
  c_queue_enqueue (evq->q, data);
  mtx_unlock (&evq->mtx);
  eventfd_write (evq->fd, 1);
}

void* c_ev_queue_deq (CEvQueue* evq)
{
  eventfd_t value;
  void* data;

  eventfd_read (evq->fd, &value);
  mtx_lock (&evq->mtx);
  data = c_queue_dequeue (evq->q);
  mtx_unlock (&evq->mtx);

  return data;
}

Attach fd of the event queue to the Gtk main loop.

  candidate->evq = c_ev_queue_new (nullptr);
  candidate->gsource = g_unix_fd_source_new (candidate->evq->fd, G_IO_IN);
  g_source_set_can_recurse (candidate->gsource, TRUE);
  g_source_set_callback (candidate->gsource, (GSourceFunc) cb_evq,
                         candidate->evq, NULL);
  g_source_attach (candidate->gsource, NULL);

While the Gtk main loop is running, if there is something to read in fd, a callback function is executed and takes one off the queue. As a test, I sent text data.

static gboolean cb_evq (gint fd, GIOCondition condition, gpointer user_data)
{
  CEvQueue* evq = (CEvQueue*) user_data;
  void* data = c_ev_queue_deq (evq);
  c_log_info ("evq: %s", (char*) data);
  return G_SOURCE_CONTINUE;
}

However, when the user activates an item in the server-side candidate window, that item must be passed to the client application. There needs to be another event queue on the main loop side of nimf to receive items.

  • thread 1: nimf main loop inside nimf server; This is the main thread.
    • contains the event queue to receive data from thread 2.
  • thread 2: Gtk main loop inside nimf server; This is a candidate window plugin.
    • contains the event queue to receive data from thread 1.

I configured and tested it like this. Even if I log in to X Window and switch to the console, nimf doesn’t stop and works fine.

However, this attempt failed. It seems to work fine, but eventually stops.