/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file x11GraphicsPipe.cxx
 * @author rdb
 * @date 2009-07-07
 */

#include "x11GraphicsPipe.h"
#include "x11GraphicsWindow.h"
#include "config_x11display.h"
#include "frameBufferProperties.h"
#include "displayInformation.h"
#include "pstrtod.h"

#include <dlfcn.h>

TypeHandle x11GraphicsPipe::_type_handle;

bool x11GraphicsPipe::_error_handlers_installed = false;
x11GraphicsPipe::ErrorHandlerFunc *x11GraphicsPipe::_prev_error_handler;
x11GraphicsPipe::IOErrorHandlerFunc *x11GraphicsPipe::_prev_io_error_handler;
bool x11GraphicsPipe::_x_error_messages_enabled = true;
int x11GraphicsPipe::_x_error_count = 0;

LightReMutex x11GraphicsPipe::_x_mutex;

/**
 *
 */
x11GraphicsPipe::
x11GraphicsPipe(const std::string &display) :
  _xcursor_size(-1),
  _have_xrandr(false),
  _XF86DGADirectVideo(nullptr) {

  std::string display_spec = display;
  if (display_spec.empty()) {
    display_spec = display_cfg;
  }
  if (display_spec.empty()) {
    display_spec = ExecutionEnvironment::get_environment_variable("DISPLAY");
  }
  if (display_spec.empty()) {
    display_spec = ":0.0";
  }

  // Store the current locale, so that we can restore it later.
  std::string saved_locale;
  char *saved_locale_p = setlocale(LC_ALL, nullptr);
  if (saved_locale_p != nullptr) {
    saved_locale = saved_locale_p;
  }

  // The X docs say we should do this to get international character support
  // from the keyboard.
  setlocale(LC_ALL, "");

  // But it's important that we use the "C" locale for numeric formatting,
  // since all of the internal Panda code assumes this--we need a decimal
  // point to mean a decimal point.
  setlocale(LC_NUMERIC, "C");

  // Also save the startup ID.  We are required to unset it by the FreeDesktop
  // specification so that it is not propagated to child processes.
  {
    char *startup_id = getenv("DESKTOP_STARTUP_ID");
    if (startup_id != nullptr) {
      _startup_id.assign(startup_id);
      if (x11display_cat.is_debug()) {
        x11display_cat.debug()
          << "Got desktop startup ID " << _startup_id << "\n";
      }
      unsetenv("DESKTOP_STARTUP_ID");
    }
  }

  _is_valid = false;
  _supported_types = OT_window | OT_buffer | OT_texture_buffer;
  _display = nullptr;
  _screen = 0;
  _root = (X11_Window)nullptr;
  _im = (XIM)nullptr;
  _hidden_cursor = None;

  // According to the documentation, we should call this before making any
  // other Xlib calls if we wish to use the Xlib locking system.
  if (x_init_threads) {
    XInitThreads();
  }

  install_error_handlers();

  _display = XOpenDisplay(display_spec.c_str());
  if (!_display) {
    x11display_cat.error()
      << "Could not open display \"" << display_spec << "\".\n";
    _is_valid = false;
    _screen = None;
    _root = None;
    _display_width = 0;
    _display_height = 0;
    return;
  }

  if (!XSupportsLocale()) {
    x11display_cat.warning()
      << "X does not support locale " << setlocale(LC_ALL, nullptr) << "\n";
  }
  XSetLocaleModifiers("");

  _screen = DefaultScreen(_display);
  _root = RootWindow(_display, _screen);
  _display_width = DisplayWidth(_display, _screen);
  _display_height = DisplayHeight(_display, _screen);
  _is_valid = true;

  // Dynamically load the xf86dga extension.
  void *xf86dga = dlopen("libXxf86dga.so.1", RTLD_NOW | RTLD_LOCAL);
  if (xf86dga != nullptr) {
    pfn_XF86DGAQueryVersion _XF86DGAQueryVersion = (pfn_XF86DGAQueryVersion)dlsym(xf86dga, "XF86DGAQueryVersion");
    _XF86DGADirectVideo = (pfn_XF86DGADirectVideo)dlsym(xf86dga, "XF86DGADirectVideo");

    int major_ver, minor_ver;
    if (_XF86DGAQueryVersion == nullptr || _XF86DGADirectVideo == nullptr) {
      x11display_cat.warning()
        << "libXxf86dga.so.1 does not provide required functions; relative mouse mode may not work.\n";

    } else if (!_XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) {
      _XF86DGADirectVideo = nullptr;
    }
  } else {
    _XF86DGADirectVideo = nullptr;
    if (x11display_cat.is_debug()) {
      x11display_cat.debug()
        << "cannot dlopen libXxf86dga.so.1; relative mouse mode may not work.\n";
    }
  }

  // Dynamically load the XCursor extension.
  void *xcursor = dlopen("libXcursor.so.1", RTLD_NOW | RTLD_LOCAL);
  if (xcursor != nullptr) {
    pfn_XcursorGetDefaultSize _XcursorGetDefaultSize = (pfn_XcursorGetDefaultSize)dlsym(xcursor, "XcursorGetDefaultSize");
    _XcursorXcFileLoadImages = (pfn_XcursorXcFileLoadImages)dlsym(xcursor, "XcursorXcFileLoadImages");
    _XcursorImagesLoadCursor = (pfn_XcursorImagesLoadCursor)dlsym(xcursor, "XcursorImagesLoadCursor");
    _XcursorImagesDestroy = (pfn_XcursorImagesDestroy)dlsym(xcursor, "XcursorImagesDestroy");
    _XcursorImageCreate = (pfn_XcursorImageCreate)dlsym(xcursor, "XcursorImageCreate");
    _XcursorImageLoadCursor = (pfn_XcursorImageLoadCursor)dlsym(xcursor, "XcursorImageLoadCursor");
    _XcursorImageDestroy = (pfn_XcursorImageDestroy)dlsym(xcursor, "XcursorImageDestroy");

    if (_XcursorGetDefaultSize == nullptr || _XcursorXcFileLoadImages == nullptr ||
        _XcursorImagesLoadCursor == nullptr || _XcursorImagesDestroy == nullptr ||
        _XcursorImageCreate == nullptr || _XcursorImageLoadCursor == nullptr ||
        _XcursorImageDestroy == nullptr) {
      _xcursor_size = -1;
      x11display_cat.warning()
        << "libXcursor.so.1 does not provide required functions; cursor changing will not work.\n";

    } else if (x_cursor_size.get_value() >= 0) {
      _xcursor_size = x_cursor_size;
    } else {
      _xcursor_size = _XcursorGetDefaultSize(_display);
    }
  } else {
    _xcursor_size = -1;
    if (x11display_cat.is_debug()) {
      x11display_cat.debug()
        << "cannot dlopen libXcursor.so.1; cursor changing will not work.\n";
    }
  }

  // Dynamically load the XRandr extension.
  void *xrandr = dlopen("libXrandr.so.2", RTLD_NOW | RTLD_LOCAL);
  if (xrandr != nullptr) {
    pfn_XRRQueryExtension _XRRQueryExtension = (pfn_XRRQueryExtension)dlsym(xrandr, "XRRQueryExtension");
    pfn_XRRQueryVersion _XRRQueryVersion = (pfn_XRRQueryVersion)dlsym(xrandr, "XRRQueryVersion");

    _XRRSizes = (pfn_XRRSizes)dlsym(xrandr, "XRRSizes");
    _XRRRates = (pfn_XRRRates)dlsym(xrandr, "XRRRates");
    _XRRGetScreenInfo = (pfn_XRRGetScreenInfo)dlsym(xrandr, "XRRGetScreenInfo");
    _XRRConfigCurrentConfiguration = (pfn_XRRConfigCurrentConfiguration)dlsym(xrandr, "XRRConfigCurrentConfiguration");
    _XRRSetScreenConfig = (pfn_XRRSetScreenConfig)dlsym(xrandr, "XRRSetScreenConfig");

    int event, error, major, minor;
    if (_XRRQueryExtension == nullptr || _XRRSizes == nullptr || _XRRRates == nullptr ||
        _XRRGetScreenInfo == nullptr || _XRRConfigCurrentConfiguration == nullptr ||
        _XRRSetScreenConfig == nullptr || _XRRQueryVersion == nullptr) {
      _have_xrandr = false;
      x11display_cat.warning()
        << "libXrandr.so.2 does not provide required functions; resolution setting will not work.\n";
    }
    else if (_XRRQueryExtension(_display, &event, &error) &&
             _XRRQueryVersion(_display, &major, &minor)) {
      _have_xrandr = true;
      if (x11display_cat.is_debug()) {
        x11display_cat.debug()
          << "Found RandR extension " << major << "." << minor << "\n";
      }

      if (major > 1 || (major == 1 && minor >= 2)) {
        if (major > 1 || (major == 1 && minor >= 3)) {
          _XRRGetScreenResourcesCurrent = (pfn_XRRGetScreenResources)
            dlsym(xrandr, "XRRGetScreenResourcesCurrent");
        } else {
          // Fall back to this slower version.
          _XRRGetScreenResourcesCurrent = (pfn_XRRGetScreenResources)
            dlsym(xrandr, "XRRGetScreenResources");
        }

        _XRRFreeScreenResources = (pfn_XRRFreeScreenResources)dlsym(xrandr, "XRRFreeScreenResources");
        _XRRGetCrtcInfo = (pfn_XRRGetCrtcInfo)dlsym(xrandr, "XRRGetCrtcInfo");
        _XRRFreeCrtcInfo = (pfn_XRRFreeCrtcInfo)dlsym(xrandr, "XRRFreeCrtcInfo");
      } else {
        _XRRGetScreenResourcesCurrent = nullptr;
        _XRRFreeScreenResources = nullptr;
        _XRRGetCrtcInfo = nullptr;
        _XRRFreeCrtcInfo = nullptr;
      }
    } else {
      _have_xrandr = false;
      if (x11display_cat.is_debug()) {
        x11display_cat.debug()
          << "RandR extension not supported; resolution setting will not work.\n";
      }
    }
  } else {
    _have_xrandr = false;
    if (x11display_cat.is_debug()) {
      x11display_cat.debug()
        << "cannot dlopen libXrandr.so.2; resolution setting will not work.\n";
    }
  }

  // Dynamically load the XInput2 extension.
  int ev, err;
  if (XQueryExtension(_display, "XInputExtension", &_xi_opcode, &ev, &err)) {
    void *xi = dlopen("libXi.so.6", RTLD_NOW | RTLD_LOCAL);
    if (xi != nullptr) {
      pfn_XIQueryVersion _XIQueryVersion = (pfn_XIQueryVersion)dlsym(xi, "XIQueryVersion");
      _XISelectEvents = (pfn_XISelectEvents)dlsym(xi, "XISelectEvents");

      int major_ver = 2, minor_ver = 0;
      if (_XIQueryVersion == nullptr || _XISelectEvents == nullptr) {
        x11display_cat.warning()
          << "libXi.so.6 does not provide required functions; relative mouse mode will not work.\n";
        _XISelectEvents = nullptr;
        dlclose(xi);

      } else if (_XIQueryVersion(_display, &major_ver, &minor_ver) == Success) {
        if (x11display_cat.is_debug()) {
          x11display_cat.debug()
            << "Found XInput extension " << major_ver << "." << minor_ver << "\n";
        }

      } else {
        if (x11display_cat.is_debug()) {
          x11display_cat.debug()
            << "XInput2 extension not supported; relative mouse mode will not work.\n";
        }
        _XISelectEvents = nullptr;
        dlclose(xi);
      }
    } else {
      _XISelectEvents = nullptr;
      if (x11display_cat.is_debug()) {
        x11display_cat.debug()
          << "cannot dlopen libXi.so.1; relative mouse mode will not work.\n";
      }
    }
  }

  // Use Xrandr to fill in the supported resolution list.
  if (_have_xrandr) {
    // If we have XRRGetScreenResources, we prefer that.  It seems to be more
    // reliable than XRRSizes in multi-monitor set-ups.
    if (auto res = get_screen_resources()) {
      if (x11display_cat.is_debug()) {
        x11display_cat.debug()
          << "Using XRRScreenResources to obtain display modes\n";
      }

      // Query current configuration, we just grab the first CRTC for now,
      // since we don't have a way to represent multiple monitors.
      RRMode current_mode_id = 0;
      if (res->ncrtc > 0) {
        if (auto info = get_crtc_info(res.get(), res->crtcs[0])) {
          current_mode_id = info->mode;
        }
      }

      _display_information->_total_display_modes = res->nmode;
      _display_information->_display_mode_array = new DisplayMode[res->nmode];
      for (int i = 0; i < res->nmode; ++i) {
        XRRModeInfo &mode = res->modes[i];

        if (mode.id == current_mode_id) {
          _display_information->_current_display_mode_index = i;
        }

        DisplayMode *dm = _display_information->_display_mode_array + i;
        dm->width = mode.width;
        dm->height = mode.height;
        dm->bits_per_pixel = -1;
        dm->fullscreen_only = false;

        if (mode.hTotal && mode.vTotal) {
          dm->refresh_rate = (double)mode.dotClock /
            ((double)mode.hTotal * (double)mode.vTotal);
        } else {
          dm->refresh_rate = 0;
        }
      }
    } else {
      if (x11display_cat.is_debug()) {
        x11display_cat.debug()
          << "Using XRRSizes and XRRRates to obtain display modes\n";
      }

      int num_sizes, num_rates;
      XRRScreenSize *xrrs;
      xrrs = _XRRSizes(_display, 0, &num_sizes);
      _display_information->_total_display_modes = 0;
      for (int i = 0; i < num_sizes; ++i) {
        _XRRRates(_display, 0, i, &num_rates);
        _display_information->_total_display_modes += num_rates;
      }

      short *rates;
      short counter = 0;
      _display_information->_display_mode_array = new DisplayMode[_display_information->_total_display_modes];
      for (int i = 0; i < num_sizes; ++i) {
        int num_rates;
        rates = _XRRRates(_display, 0, i, &num_rates);
        for (int j = 0; j < num_rates; ++j) {
          DisplayMode* dm = _display_information->_display_mode_array + counter;
          dm->width = xrrs[i].width;
          dm->height = xrrs[i].height;
          dm->refresh_rate = rates[j];
          dm->bits_per_pixel = -1;
          dm->fullscreen_only = false;
          ++counter;
        }
      }
    }
  }

  // Connect to an input method for supporting international text entry.
  _im = XOpenIM(_display, nullptr, nullptr, nullptr);
  if (_im == (XIM)nullptr) {
    // Fall back to internal input method.
    XSetLocaleModifiers("@im=none");
    _im = XOpenIM(_display, nullptr, nullptr, nullptr);
    if (_im == (XIM)nullptr) {
      x11display_cat.warning()
        << "Couldn't open input method.\n";
    }
  }

  // What styles does the current input method support?
  /*
  XIMStyles *im_supported_styles;
  XGetIMValues(_im, XNQueryInputStyle, &im_supported_styles, NULL);

  for (int i = 0; i < im_supported_styles->count_styles; i++) {
    XIMStyle style = im_supported_styles->supported_styles[i];
    cerr << "style " << i << ". " << hex << style << dec << "\n";
  }

  XFree(im_supported_styles);
  */

  // Restore the previous locale.
  if (!saved_locale.empty()) {
    setlocale(LC_ALL, saved_locale.c_str());
  }

  const char *dpi = XGetDefault(_display, "Xft", "dpi");
  if (dpi != nullptr) {
    char *endptr = nullptr;
    double result = pstrtod(dpi, &endptr);
    if (result != 0 && !cnan(result) && endptr[0] == '\0') {
      result /= 96;
      set_detected_display_zoom(result);

      if (x11display_cat.is_debug()) {
        x11display_cat.debug()
          << "Determined display zoom to be " << result
          << " based on specified Xft.dpi " << dpi << "\n";
      }
    } else {
      x11display_cat.warning()
        << "Unable to determine display zoom because Xft.dpi is invalid: "
        << dpi << "\n";
    }
  } else if (x11display_cat.is_debug()) {
    x11display_cat.debug()
      << "Unable to determine display zoom because Xft.dpi was not set.\n";
  }

  // Get some X atom numbers.
  _utf8_string = XInternAtom(_display, "UTF8_STRING", false);
  _wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", false);
  _net_startup_id = XInternAtom(_display, "_NET_STARTUP_ID", false);
  _net_startup_info = XInternAtom(_display, "_NET_STARTUP_INFO", false);
  _net_startup_info_begin = XInternAtom(_display, "_NET_STARTUP_INFO_BEGIN", false);
  _net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false);
  _net_wm_name = XInternAtom(_display, "_NET_WM_NAME", false);
  _net_wm_pid = XInternAtom(_display, "_NET_WM_PID", false);
  _net_wm_ping = XInternAtom(_display, "_NET_WM_PING", false);
  _net_wm_state = XInternAtom(_display, "_NET_WM_STATE", false);
  _net_wm_state_above = XInternAtom(_display, "_NET_WM_STATE_ABOVE", false);
  _net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false);
  _net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false);
  _net_wm_state_fullscreen = XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", false);
  _net_wm_state_maximized_horz = XInternAtom(_display, "_NET_WM_STATE_MAXIMIZED_HORZ", false);
  _net_wm_state_maximized_vert = XInternAtom(_display, "_NET_WM_STATE_MAXIMIZED_VERT", false);
  _net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false);
  _net_wm_window_type = XInternAtom(_display, "_NET_WM_WINDOW_TYPE", false);
  _net_wm_window_type_fullscreen = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_FULLSCREEN", false);
  _net_wm_window_type_splash = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_SPLASH", false);
}

/**
 *
 */
x11GraphicsPipe::
~x11GraphicsPipe() {
  release_hidden_cursor();
  if (_im) {
    XCloseIM(_im);
  }
  if (_display) {
    XCloseDisplay(_display);
  }
}

/**
 * Tells the window manager that the launch sequence with the indicated startup
 * ID has finished.  Will only send it once, and only if DESKTOP_STARTUP_ID was
 * passed in as an environment variable.
 */
void x11GraphicsPipe::
send_startup_notification() {
  if (_sent_startup_notification || _startup_id.empty()) {
    return;
  }

  // Allocate enough room for the message, with room for escape characters.
  char *message = (char *)alloca(_startup_id.size() * 2 + 14);
  memcpy(message, "remove: ID=\"", 12);

  char *p = message + 12;
  for (char c : _startup_id) {
    if (c == '"' || c == '\\') {
      *p++ = '\\';
    }
    *p++ = c;
  }
  *p++ = '\"';
  *p++ = '\0';

  if (x11display_cat.is_debug()) {
    x11display_cat.debug()
      << "Sending startup info message: " << message << "\n";
  }

  // It doesn't *strictly* seem to be necessary to create a window for this
  // (just passing in the root window works too) but the spec says we should
  // and GTK does it too, so why not?
  XSetWindowAttributes attrs;
  attrs.override_redirect = True;
  attrs.event_mask = PropertyChangeMask | StructureNotifyMask;
  X11_Window xwin = XCreateWindow(_display, _root, -100, -100, 1, 1, 0,
                                  CopyFromParent, CopyFromParent, CopyFromParent,
                                  CWOverrideRedirect | CWEventMask, &attrs);

  XEvent xevent;
  xevent.xclient.type = ClientMessage;
  xevent.xclient.message_type = _net_startup_info_begin;
  xevent.xclient.display = _display;
  xevent.xclient.window = xwin;
  xevent.xclient.format = 8;

  const char *src = message;
  const char *src_end = message + strlen(message) + 1;

  char *dest, *dest_end;
  while (src != src_end) {
    dest = &xevent.xclient.data.b[0];
    dest_end = dest + 20;

    while (dest != dest_end && src != src_end) {
      *dest = *src;
      ++dest;
      ++src;
    }

    while (dest != dest_end) {
      *dest = '\0';
      ++dest;
    }

    XSendEvent(_display, _root, False, PropertyChangeMask, &xevent);
    xevent.xclient.message_type = _net_startup_info;
  }

  XDestroyWindow(_display, xwin);
  XFlush(_display);

  _sent_startup_notification = true;
}

/**
 * Enables raw mouse mode for this display.  Returns false if unsupported.
 */
bool x11GraphicsPipe::
enable_raw_mouse() {
  if (_num_raw_mouse_windows > 0) {
    // Already enabled by another window.
    ++_num_raw_mouse_windows;
    return true;
  }
  if (_XISelectEvents != nullptr) {
    XIEventMask event_mask;
    unsigned char mask[XIMaskLen(XI_RawMotion)] = {0};

    event_mask.deviceid = XIAllMasterDevices;
    event_mask.mask_len = sizeof(mask);
    event_mask.mask = mask;
    XISetMask(mask, XI_RawMotion);

    if (_XISelectEvents(_display, _root, &event_mask, 1) == Success) {
      if (x11display_cat.is_info()) {
        x11display_cat.info()
          << "Enabled raw mouse events using XInput2 extension\n";
      }
      ++_num_raw_mouse_windows;
      return true;
    }
  }
  return false;
}

/**
 * Disables raw mouse mode for this display.
 */
void x11GraphicsPipe::
disable_raw_mouse() {
  if (--_num_raw_mouse_windows == 0) {
    if (x11display_cat.is_debug()) {
      x11display_cat.debug()
        << "Disabling raw mouse events using XInput2 extension\n";
    }

    XIEventMask event_mask;
    unsigned char mask[] = {0};

    event_mask.deviceid = XIAllMasterDevices;
    event_mask.mask_len = sizeof(mask);
    event_mask.mask = mask;

    _XISelectEvents(_display, _root, &event_mask, 1);
  }
}

/**
 * Returns an XRRScreenResources object, or null if RandR 1.2 is not supported.
 */
std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources> x11GraphicsPipe::
get_screen_resources() const {
  XRRScreenResources *res = nullptr;

  if (_have_xrandr && _XRRGetScreenResourcesCurrent != nullptr) {
    res = _XRRGetScreenResourcesCurrent(_display, _root);
  }

  return std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources>(res, _XRRFreeScreenResources);
}

/**
 * Returns an XRRCrtcInfo object, or null if RandR 1.2 is not supported.
 */
std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo> x11GraphicsPipe::
get_crtc_info(XRRScreenResources *res, RRCrtc crtc) const {
  XRRCrtcInfo *info = nullptr;

  if (_have_xrandr && _XRRGetCrtcInfo != nullptr) {
    info = _XRRGetCrtcInfo(_display, res, crtc);
  }

  return std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo>(info, _XRRFreeCrtcInfo);
}

/**
 * Finds a CRTC for going fullscreen to, at the given origin.  The new CRTC
 * is returned, along with its x, y, width and height.
 *
 * If the required RandR extension is not supported, a value of None will be
 * returned, but x, y, width and height will still be populated.
 */
RRCrtc x11GraphicsPipe::
find_fullscreen_crtc(const LPoint2i &point,
                     int &x, int &y, int &width, int &height) {
  x = 0;
  y = 0;
  width = DisplayWidth(_display, _screen);
  height = DisplayHeight(_display, _screen);

  if (auto res = get_screen_resources()) {
    for (int i = 0; i < res->ncrtc; ++i) {
      RRCrtc crtc = res->crtcs[i];
      if (auto info = get_crtc_info(res.get(), crtc)) {
        if (point[0] >= info->x && point[0] < info->x + (int)info->width &&
            point[1] >= info->y && point[1] < info->y + (int)info->height) {

          x = info->x;
          y = info->y;
          width = (int)info->width;
          height = (int)info->height;
          return crtc;
        }
      }
    }
  }

  return None;
}

/**
 * Returns an indication of the thread in which this GraphicsPipe requires its
 * window processing to be performed: typically either the app thread (e.g.
 * X) or the draw thread (Windows).
 */
GraphicsPipe::PreferredWindowThread
x11GraphicsPipe::get_preferred_window_thread() const {
  // Actually, since we're creating the graphics context in open_window() now,
  // it appears we need to ensure the open_window() call is performed in the
  // draw thread for now, even though X wants all of its calls to be single-
  // threaded.

  // This means that all X windows may have to be handled by the same draw
  // thread, which we didn't intend (though the global _x_mutex may allow them
  // to be technically served by different threads, even though the actual X
  // calls will be serialized).  There might be a better way.

  return PWT_draw;
}

/**
 * Called once to make an invisible Cursor for return from
 * get_hidden_cursor().
 */
void x11GraphicsPipe::
make_hidden_cursor() {
  nassertv(_hidden_cursor == None);

  unsigned int x_size, y_size;
  XQueryBestCursor(_display, _root, 1, 1, &x_size, &y_size);

  Pixmap empty = XCreatePixmap(_display, _root, x_size, y_size, 1);

  XColor black;
  memset(&black, 0, sizeof(black));

  _hidden_cursor = XCreatePixmapCursor(_display, empty, empty,
                                       &black, &black, x_size, y_size);
  XFreePixmap(_display, empty);
}

/**
 * Called once to release the invisible cursor created by
 * make_hidden_cursor().
 */
void x11GraphicsPipe::
release_hidden_cursor() {
  if (_hidden_cursor != None) {
    XFreeCursor(_display, _hidden_cursor);
    _hidden_cursor = None;
  }
}

/**
 * Installs new Xlib error handler functions if this is the first time this
 * function has been called.  These error handler functions will attempt to
 * reduce Xlib's annoying tendency to shut down the client at the first error.
 * Unfortunately, it is difficult to play nice with the client if it has
 * already installed its own error handlers.
 */
void x11GraphicsPipe::
install_error_handlers() {
  if (_error_handlers_installed) {
    return;
  }

  _prev_error_handler = (ErrorHandlerFunc *)XSetErrorHandler(error_handler);
  _prev_io_error_handler = (IOErrorHandlerFunc *)XSetIOErrorHandler(io_error_handler);
  _error_handlers_installed = true;
}

/**
 * This function is installed as the error handler for a non-fatal Xlib error.
 */
int x11GraphicsPipe::
error_handler(X11_Display *display, XErrorEvent *error) {
  ++_x_error_count;

  static const int msg_len = 80;
  char msg[msg_len];
  XGetErrorText(display, error->error_code, msg, msg_len);

  if (!_x_error_messages_enabled) {
    if (x11display_cat.is_debug()) {
      x11display_cat.debug()
        << msg << "\n";
    }
    return 0;
  }

  x11display_cat.error()
    << msg << "\n";

  if (x_error_abort) {
    abort();
  }

  // We return to allow the application to continue running, unlike the
  // default X error handler which exits.
  return 0;
}

/**
 * This function is installed as the error handler for a fatal Xlib error.
 */
int x11GraphicsPipe::
io_error_handler(X11_Display *display) {
  x11display_cat.fatal()
    << "X fatal error on display " << (void *)display << "\n";

  // Unfortunately, we can't continue from this function, even if we promise
  // never to use X again.  We're supposed to terminate without returning, and
  // if we do return, the caller will exit anyway.  Sigh.  Very poor design on
  // X's part.
  return 0;
}
