Skip to content

4.20.1-1: When no monitor is connected, xfdesktop-settings shows two error messages

Issue Description

If the user runs the Xfce desktop remotely (example, using VNC) and the PC doesn't have a monitor connected, and the user runs the Xfce Desktop Settings app (Applications -> Settings -> Desktop), the app shows an error message box: "Unable to load images from folder "(null)". Operation was cancelled".

Actually, there are TWO error message boxes, stacked on top of each other. You can move the top one aside and see the 2nd box

Steps to Reproduce Issue

  • Requirements
    • Another PC with a VNC client installed.
  • Setup (VNC server)
    • Install a VNC server: sudo apt-get install x11vnc
    • Manually run it in a console: x11vnc -display :0 -forever
    • Connect the 2nd PC's VNC client to the test PC.
  • Reproduce the bug
    • Unplug all video cables from the PC.
    • Open a console and run "xrandr" to confirm that no monitors are connected.
    • Launch the Xfce Desktop settings app: Applications -> Settings -> Desktop

Expected Behavior

In Debian 12 Bookworm's xfdesktop4 (4.18.1-1), no error messages are displayed.

Additional Information

  • Analysis
    • The error message is displayed in xfdesktop_image_list_add_dir(), which is an asynchronous callback function triggered by g_file_enumerate_children_async(), used to get info about files in a directory.

      What's happening is that when there's no monitor connected, g_file_enumerate_children_async() is called 3 times. I suspect that each subsequent request must be cancelling out the previous asynchronous request, causing the error "Operation was cancelled".

      But when there IS a monitor connected, my debugging shows that g_file_enumerate_children_async() is called only ONCE. So there's no overlapping async request, no errors.

    • The root cause for why there are 3 async requests when monitorless is in cb_update_background_tab(). During the app initialization, this function is called multiple times, and there's logic to prevent it from doing the same thing multiple times. Here's a condensed version of the code:

          gchar *monitor_name = 
    xfdesktop_get_monitor_name_from_gtk_widget(background_settings->image_iconview, 
    monitor_num);
          
          /* Most of the time we won't change monitor, screen, or workspace so try
           * to bail out now if we can */
          /* Check to see if we haven't changed workspace or screen */
          if (background_settings->workspace == workspace_num && 
    background_settings->screen == screen_num) {
              /* do we support monitor names? */
              if(background_settings->monitor_name != NULL || monitor_name != NULL) {
                  /* Check if we haven't changed monitors (by name) */
    if(g_strcmp0(background_settings->monitor_name, monitor_name) == 0) {
                      g_free(monitor_name);
                      return;
                  }
              }
          }
          
          TRACE("screen, monitor, or workspace changed");
          background_settings->monitor_name = 
    xfdesktop_get_monitor_name_from_gtk_widget(background_settings->image_iconview, 
    monitor_num);
          
          <The code logic here eventually triggers the async request 
    g_file_enumerate_children_async().>

The code first gets the current monitor name. Example from my debugging: "VGA-0". The code then checks if the monitor has changed. If it has NOT changed, the code returns early. When there's a monitor, this works correctly, so the only time this code proceeds ahead is the first time, when "background_settings->monitor_name" is NULL and when the code gets the monitor name.

The problem is that when there is NO monitor, xfdesktop_get_monitor_name_from_gtk_widget() returns a NULL monitor name. Since there's no monitor name, there's no way to check if the monitor name has changed. So the code proceeds ahead to the async request. And since cb_update_background_tab() is called several times during initialization, this code logic triggers several overlapping async requests.

Failed Solution Attempts

  • I don't have a good proposed solution, only a couple of failed solutions.

    The problem is that when "background_settings->monitor_name" is NULL, there's no way to know if this NULL means "uninitialized - We haven't checked what monitors are connected", or if it means "We know there's no monitor connected".

  • The first bad solution is to return early if both monitor names are NULL. But the problem is that the code NEVER proceeds ahead to do the async call, so the GUI never initializes the available background images.


      if(background_settings->monitor_name == NULL && monitor_name == NULL) {
          g_free(monitor_name);
          return;
      }
  • The second solution was to change xfdesktop_get_monitor_name_from_gtk_widget() from returning NULL to returning an empty string. In this solution, when there's no monitor, "background_settings->monitor_name" is set to an empty string, which clearly indicates that there's currently no monitor.

      gchar*
      xfdesktop_get_monitor_name_from_gtk_widget(GtkWidget *widget, gint 
monitor_num)
      {
          <...>
          monitor = gdk_display_get_monitor(display, monitor_num);

          const gchar *monitor_model = gdk_monitor_get_model(monitor);

          return g_strdup(monitor_model != NULL ? monitor_model : "");
      }

This seems like a better solution. But my concern is that "background_settings->monitor_name" is intended to be NULL or have a valid monitor name. I don't know what the side effects are of having an empty monitor_name string.

Debian bug URL

If you need this, here's where I filed the bug originally: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1119965

Edited by LogicFinity