panda3d/panda/src/x11display/x11GraphicsWindow.cxx
2021-07-02 16:03:41 +02:00

2513 lines
79 KiB
C++

/**
* 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 x11GraphicsWindow.cxx
* @author rdb
* @date 2009-07-07
*/
#include "x11GraphicsWindow.h"
#include "config_x11display.h"
#include "x11GraphicsPipe.h"
#include "graphicsPipe.h"
#include "keyboardButton.h"
#include "mouseButton.h"
#include "buttonMap.h"
#include "clockObject.h"
#include "pStatTimer.h"
#include "textEncoder.h"
#include "throw_event.h"
#include "lightReMutexHolder.h"
#include "nativeWindowHandle.h"
#include "virtualFileSystem.h"
#include "get_x11.h"
#include "pnmImage.h"
#include "pnmFileTypeRegistry.h"
#include "evdevInputDevice.h"
#include <sys/time.h>
#include <fcntl.h>
using std::istream;
using std::ostringstream;
using std::string;
struct _XcursorFile {
void *closure;
int (*read)(XcursorFile *, unsigned char *, int);
int (*write)(XcursorFile *, unsigned char *, int);
int (*seek)(XcursorFile *, long, int);
};
typedef struct _XcursorImage {
unsigned int version;
unsigned int size;
unsigned int width;
unsigned int height;
unsigned int xhot;
unsigned int yhot;
unsigned int delay;
unsigned int *pixels;
} XcursorImage;
static int xcursor_read(XcursorFile *file, unsigned char *buf, int len) {
istream* str = (istream*) file->closure;
str->read((char*) buf, len);
return str->gcount();
}
static int xcursor_write(XcursorFile *file, unsigned char *buf, int len) {
// Not implemented, we don't need it.
nassertr_always(false, 0);
return 0;
}
static int xcursor_seek(XcursorFile *file, long offset, int whence) {
istream* str = (istream*) file->closure;
switch (whence) {
case SEEK_SET:
str->seekg(offset, istream::beg);
break;
case SEEK_CUR:
str->seekg(offset, istream::cur);
break;
case SEEK_END:
str->seekg(offset, istream::end);
}
return str->tellg();
}
TypeHandle x11GraphicsWindow::_type_handle;
/**
*
*/
x11GraphicsWindow::
x11GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
const string &name,
const FrameBufferProperties &fb_prop,
const WindowProperties &win_prop,
int flags,
GraphicsStateGuardian *gsg,
GraphicsOutput *host) :
GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
{
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
_display = x11_pipe->get_display();
_screen = x11_pipe->get_screen();
_xwindow = (X11_Window)nullptr;
_ic = (XIC)nullptr;
_visual_info = nullptr;
_orig_size_id = -1;
if (x11_pipe->_have_xrandr) {
// We may still need these functions after the pipe is already destroyed,
// so we copy them into the x11GraphicsWindow.
_XRRGetScreenInfo = x11_pipe->_XRRGetScreenInfo;
_XRRSetScreenConfig = x11_pipe->_XRRSetScreenConfig;
}
_awaiting_configure = false;
_dga_mouse_enabled = false;
_raw_mouse_enabled = false;
_override_redirect = False;
_wm_delete_window = x11_pipe->_wm_delete_window;
PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
add_input_device(device);
_input = device;
}
/**
*
*/
x11GraphicsWindow::
~x11GraphicsWindow() {
if (!_cursor_filenames.empty()) {
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
for (auto item : _cursor_filenames) {
XFreeCursor(_display, item.second);
}
}
}
/**
* Returns the MouseData associated with the nth input device's pointer. This
* is deprecated; use get_pointer_device().get_pointer() instead, or for raw
* mice, use the InputDeviceManager interface.
*/
MouseData x11GraphicsWindow::
get_pointer(int device) const {
MouseData result;
{
LightMutexHolder holder(_input_lock);
nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData());
result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer();
// We recheck this immediately to get the most up-to-date value, but we
// won't bother waiting for the lock if we can't.
if (device == 0 && !_dga_mouse_enabled && !_raw_mouse_enabled &&
result._in_window && x11GraphicsPipe::_x_mutex.try_lock()) {
XEvent event;
if (_xwindow != None &&
XQueryPointer(_display, _xwindow, &event.xbutton.root,
&event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root,
&event.xbutton.x, &event.xbutton.y, &event.xbutton.state)) {
double time = ClockObject::get_global_clock()->get_real_time();
result._xpos = event.xbutton.x;
result._ypos = event.xbutton.y;
((GraphicsWindowInputDevice *)_input_devices[0].p())->set_pointer_in_window(result._xpos, result._ypos, time);
}
x11GraphicsPipe::_x_mutex.release();
}
}
return result;
}
/**
* Forces the pointer to the indicated position within the window, if
* possible.
*
* Returns true if successful, false on failure. This may fail if the mouse
* is not currently within the window, or if the API doesn't support this
* operation.
*/
bool x11GraphicsWindow::
move_pointer(int device, int x, int y) {
// Note: this is not thread-safe; it should be called only from App.
// Probably not an issue.
if (device == 0) {
// Move the system mouse pointer.
PointerData md = _input->get_pointer();
if (!_properties.get_foreground() || !md.get_in_window()) {
// If the window doesn't have input focus, or the mouse isn't currently
// within the window, forget it.
return false;
}
if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) {
if (!_dga_mouse_enabled) {
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
XWarpPointer(_display, None, _xwindow, 0, 0, 0, 0, x, y);
}
_input->set_pointer_in_window(x, y);
}
return true;
} else {
// Can't move a raw mouse.
return false;
}
}
/**
* Clears the entire framebuffer before rendering, according to the settings
* of get_color_clear_active() and get_depth_clear_active() (inherited from
* DrawableRegion).
*
* This function is called only within the draw thread.
*/
void x11GraphicsWindow::
clear(Thread *current_thread) {
if (is_any_clear_active()) {
// Evidently the NVIDIA driver may call glXCreateNewContext inside
// prepare_display_region, so we need to hold the X11 lock.
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
GraphicsOutput::clear(current_thread);
}
}
/**
* This function will be called within the draw thread before beginning
* rendering for a given frame. It should do whatever setup is required, and
* return true if the frame should be rendered, or false if it should be
* skipped.
*/
bool x11GraphicsWindow::
begin_frame(FrameMode mode, Thread *current_thread) {
PStatTimer timer(_make_current_pcollector, current_thread);
begin_frame_spam(mode);
if (_gsg == nullptr) {
return false;
}
if (_awaiting_configure) {
// Don't attempt to draw while we have just reconfigured the window and we
// haven't got the notification back yet.
return false;
}
// Reset the GSG state if this is the first time it has been used. (We
// can't just call reset() when we construct the GSG, because reset()
// requires having a current context.)
_gsg->reset_if_new();
if (mode == FM_render) {
// begin_render_texture();
clear_cube_map_selection();
}
_gsg->set_current_properties(&get_fb_properties());
return _gsg->begin_frame(current_thread);
}
/**
* This function will be called within the draw thread after rendering is
* completed for a given frame. It should do whatever finalization is
* required.
*/
void x11GraphicsWindow::
end_frame(FrameMode mode, Thread *current_thread) {
end_frame_spam(mode);
nassertv(_gsg != nullptr);
if (mode == FM_render) {
// end_render_texture();
copy_to_textures();
}
_gsg->end_frame(current_thread);
if (mode == FM_render) {
trigger_flip();
clear_cube_map_selection();
}
}
/**
* Do whatever processing is necessary to ensure that the window responds to
* user events. Also, honor any requests recently made via
* request_properties()
*
* This function is called only within the window thread.
*/
void x11GraphicsWindow::
process_events() {
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
GraphicsWindow::process_events();
if (_xwindow == (X11_Window)0) {
return;
}
XEvent event;
XKeyEvent keyrelease_event;
bool got_keyrelease_event = false;
XConfigureEvent configure_event;
bool got_configure_event = false;
WindowProperties properties;
bool changed_properties = false;
XPropertyEvent property_event;
bool got_net_wm_state_change = false;
while (XCheckIfEvent(_display, &event, check_event, (char *)this)) {
if (got_keyrelease_event) {
// If a keyrelease event is immediately followed by a matching keypress
// event, that's just key repeat and we should treat the two events
// accordingly. It would be nice if X provided a way to differentiate
// between keyrepeat and explicit keypresses more generally.
got_keyrelease_event = false;
if (event.type == KeyPress &&
event.xkey.keycode == keyrelease_event.keycode &&
(event.xkey.time - keyrelease_event.time <= 1)) {
if (!XFilterEvent(&event, None)) {
// In particular, we only generate down messages for the repeated
// keys, not down-and-up messages.
handle_keystroke(event.xkey);
// We thought about not generating the keypress event, but we need
// that repeat for backspace. Rethink later.
handle_keypress(event.xkey);
}
continue;
} else {
// This keyrelease event is not immediately followed by a matching
// keypress event, so it's a genuine release.
ButtonHandle raw_button = map_raw_button(keyrelease_event.keycode);
if (raw_button != ButtonHandle::none()) {
_input->raw_button_up(raw_button);
}
handle_keyrelease(keyrelease_event);
}
}
// Send out a raw key press event before we do XFilterEvent, which will
// filter out dead keys and such.
if (event.type == KeyPress) {
ButtonHandle raw_button = map_raw_button(event.xkey.keycode);
if (raw_button != ButtonHandle::none()) {
_input->raw_button_down(raw_button);
}
}
if (XFilterEvent(&event, None)) {
continue;
}
ButtonHandle button;
switch (event.type) {
case ReparentNotify:
break;
case PropertyNotify:
//std::cout << "PropertyNotify event: atom = " << event.xproperty.atom << std::endl;
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
if (event.xproperty.atom == x11_pipe->_net_wm_state) {
// currently we're only interested in the net_wm_state type of
// changes and only need to gather property informations once at
// the end after the while loop
property_event = event.xproperty;
got_net_wm_state_change = true;
}
break;
case ConfigureNotify:
// When resizing or moving the window, multiple ConfigureNotify events
// may be sent in rapid succession. We only respond to the last one.
configure_event = event.xconfigure;
got_configure_event = true;
break;
case ButtonPress:
// This refers to the mouse buttons.
button = get_mouse_button(event.xbutton);
if (_properties.get_mouse_mode() != WindowProperties::M_relative) {
_input->set_pointer_in_window(event.xbutton.x, event.xbutton.y);
}
_input->button_down(button);
break;
case ButtonRelease:
button = get_mouse_button(event.xbutton);
if (_properties.get_mouse_mode() != WindowProperties::M_relative) {
_input->set_pointer_in_window(event.xbutton.x, event.xbutton.y);
}
_input->button_up(button);
break;
case MotionNotify:
if (!_raw_mouse_enabled) {
if (_dga_mouse_enabled) {
PointerData md = _input->get_pointer();
_input->set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root);
} else {
_input->set_pointer_in_window(event.xmotion.x, event.xmotion.y);
}
}
break;
case GenericEvent:
if (_raw_mouse_enabled) {
XGenericEventCookie *cookie = &event.xcookie;
XGetEventData(_display, cookie);
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
if (cookie->evtype == XI_RawMotion &&
cookie->extension == x11_pipe->_xi_opcode) {
const XIRawEvent *raw_event = (const XIRawEvent *)cookie->data;
const double *values = raw_event->raw_values;
double x = 0, y = 0;
if (XIMaskIsSet(raw_event->valuators.mask, 0)) {
x = values[0];
}
if (XIMaskIsSet(raw_event->valuators.mask, 1)) {
y = values[1];
}
PointerData md = _input->get_pointer();
_input->set_pointer_in_window(md.get_x() + x, md.get_y() + y);
}
XFreeEventData(_display, cookie);
}
break;
case KeyPress:
handle_keystroke(event.xkey);
handle_keypress(event.xkey);
break;
case KeyRelease:
// The KeyRelease can't be processed immediately, because we have to
// check first if it's immediately followed by a matching KeyPress
// event.
keyrelease_event = event.xkey;
got_keyrelease_event = true;
break;
case EnterNotify:
if (_properties.get_mouse_mode() == WindowProperties::M_relative) {
PointerData md = _input->get_pointer();
_input->set_pointer_in_window(md.get_x(), md.get_y());
} else {
_input->set_pointer_in_window(event.xcrossing.x, event.xcrossing.y);
}
break;
case LeaveNotify:
_input->set_pointer_out_of_window();
break;
case FocusIn:
properties.set_foreground(true);
changed_properties = true;
break;
case FocusOut:
_input->focus_lost();
properties.set_foreground(false);
changed_properties = true;
break;
case UnmapNotify:
properties.set_minimized(true);
changed_properties = true;
break;
case MapNotify:
properties.set_minimized(false);
changed_properties = true;
// Auto-focus the window when it is mapped.
XSetInputFocus(_display, _xwindow, RevertToPointerRoot, CurrentTime);
break;
case ClientMessage:
if ((Atom)(event.xclient.data.l[0]) == _wm_delete_window) {
// This is a message from the window manager indicating that the user
// has requested to close the window.
string close_request_event = get_close_request_event();
if (!close_request_event.empty()) {
// In this case, the app has indicated a desire to intercept the
// request and process it directly.
throw_event(close_request_event);
} else {
// In this case, the default case, the app does not intend to
// service the request, so we do by closing the window.
// TODO: don't release the gsg in the window thread.
close_window();
properties.set_open(false);
system_changed_properties(properties);
}
}
break;
case DestroyNotify:
// Apparently, we never get a DestroyNotify on a toplevel window.
// Instead, we rely on hints from the window manager (see above).
x11display_cat.info()
<< "DestroyNotify\n";
break;
default:
x11display_cat.warning()
<< "unhandled X event type " << event.type << "\n";
}
}
if (got_configure_event) {
// Now handle the last configure event we found.
_awaiting_configure = false;
// Is this the inner corner or the outer corner? The Xlib docs say it
// should be the outer corner, but it appears to be the inner corner on my
// own implementation, which is inconsistent with XConfigureWindow.
// (Panda really wants to work with the inner corner, anyway, but that
// means we need to fix XConfigureWindow too.)
properties.set_origin(configure_event.x, configure_event.y);
properties.set_size(configure_event.width, configure_event.height);
if (_properties.get_fixed_size()) {
// If the window properties indicate a fixed size only, undo any attempt
// by the user to change them. In X, there doesn't appear to be a way
// to universally disallow this directly (although we do set the
// min_size and max_size to the same value, which seems to work for most
// window managers.)
if (configure_event.width != _fixed_size.get_x() ||
configure_event.height != _fixed_size.get_y()) {
XWindowChanges changes;
changes.width = _fixed_size.get_x();
changes.height = _fixed_size.get_y();
int value_mask = (CWWidth | CWHeight);
XConfigureWindow(_display, _xwindow, value_mask, &changes);
}
}
// If the window was reconfigured, we may need to re-confine the mouse
// pointer. See GitHub bug #280.
if (_properties.get_mouse_mode() == WindowProperties::M_confined) {
X11_Cursor cursor = None;
if (_properties.get_cursor_hidden()) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
cursor = x11_pipe->get_hidden_cursor();
}
XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, GrabModeAsync,
_xwindow, cursor, CurrentTime);
}
changed_properties = true;
}
if (properties.has_foreground() &&
(_properties.get_mouse_mode() != WindowProperties::M_absolute)) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
// Focus has changed, let's let go of the pointer if we've grabbed or re-grab it if needed
if (properties.get_foreground()) {
// Window is going to the foreground, re-grab the pointer
X11_Cursor cursor = None;
if (_properties.get_cursor_hidden()) {
cursor = x11_pipe->get_hidden_cursor();
}
XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, GrabModeAsync,
_xwindow, cursor, CurrentTime);
if (_dga_mouse_enabled) {
x11_pipe->enable_dga_mouse();
}
}
else {
// window is leaving the foreground, ungrab the pointer
if (_dga_mouse_enabled) {
x11_pipe->disable_dga_mouse();
} else if (_properties.get_mouse_mode() == WindowProperties::M_confined) {
XUngrabPointer(_display, CurrentTime);
}
}
}
if (got_net_wm_state_change) {
// some wm state properties have been changed, check their values
// once in this part instead of multiple times in the while loop
// Check if this window is maximized or not
bool is_maximized = false;
Atom wmState = property_event.atom;
Atom type;
int format;
unsigned long nItem, bytesAfter;
unsigned char *new_window_properties = NULL;
// gather all properties from the active dispplay and window
XGetWindowProperty(_display, _xwindow, wmState, 0, LONG_MAX, false, AnyPropertyType, &type, &format, &nItem, &bytesAfter, &new_window_properties);
if (nItem > 0) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
// run through all found items
for (unsigned long iItem = 0; iItem < nItem; ++iItem) {
unsigned long item = reinterpret_cast<unsigned long *>(new_window_properties)[iItem];
// check if the item is one of the maximized states
if (item == x11_pipe->_net_wm_state_maximized_horz ||
item == x11_pipe->_net_wm_state_maximized_vert) {
// The window was maximized
is_maximized = true;
}
}
}
// Debug entry
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "set maximized to: " << is_maximized << "\n";
}
// Now make sure the property will get stored correctly
properties.set_maximized(is_maximized);
changed_properties = true;
}
if (changed_properties) {
system_changed_properties(properties);
}
if (got_keyrelease_event) {
// This keyrelease event is not immediately followed by a matching
// keypress event, so it's a genuine release.
ButtonHandle raw_button = map_raw_button(keyrelease_event.keycode);
if (raw_button != ButtonHandle::none()) {
_input->raw_button_up(raw_button);
}
handle_keyrelease(keyrelease_event);
}
}
/**
* Applies the requested set of properties to the window, if possible, for
* instance to request a change in size or minimization status.
*
* The window properties are applied immediately, rather than waiting until
* the next frame. This implies that this method may *only* be called from
* within the window thread.
*
* The return value is true if the properties are set, false if they are
* ignored. This is mainly useful for derived classes to implement extensions
* to this function.
*/
void x11GraphicsWindow::
set_properties_now(WindowProperties &properties) {
if (_pipe == nullptr) {
// If the pipe is null, we're probably closing down.
GraphicsWindow::set_properties_now(properties);
return;
}
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
// We're either going into or out of fullscreen, or are in fullscreen and
// are changing the resolution.
bool is_fullscreen = _properties.has_fullscreen() && _properties.get_fullscreen();
bool want_fullscreen = properties.has_fullscreen() ? properties.get_fullscreen() : is_fullscreen;
if (want_fullscreen && properties.has_origin()) {
// If we're fullscreen, reject changes to the origin.
properties.clear_origin();
}
if (is_fullscreen != want_fullscreen || (is_fullscreen && properties.has_size())) {
if (want_fullscreen) {
// OK, first figure out which CRTC the window is on. It may be on more
// than one, actually, so grab a point in the center in order to figure
// out which one it's more-or-less mostly on.
LPoint2i center(0, 0);
if (_properties.has_origin()) {
center = _properties.get_origin();
if (_properties.has_size()) {
center += _properties.get_size() / 2;
}
}
int x, y, width, height;
x11_pipe->find_fullscreen_crtc(center, x, y, width, height);
// Which size should we go fullscreen in?
int reqsizex, reqsizey;
if (properties.has_size()) {
reqsizex = properties.get_x_size();
reqsizey = properties.get_y_size();
} else if (_properties.has_size()) {
reqsizex = _properties.get_x_size();
reqsizey = _properties.get_y_size();
} else {
reqsizex = width;
reqsizey = height;
}
// Are we passing in pipe.display_width/height? This is actually the
// size of the virtual desktop, which may not be a real resolution, so
// if that is passed in, we have to assume that the user means to just
// fullscreen without changing the screen resolution.
if ((reqsizex == x11_pipe->get_display_width() &&
reqsizey == x11_pipe->get_display_height())
|| (width == reqsizex && height == reqsizey)
|| !x11_pipe->_have_xrandr) {
// Cover the current CRTC.
properties.set_origin(x, y);
properties.set_size(width, height);
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Setting window to fullscreen on CRTC "
<< width << "x" << height << "+" << x << "+" << y << "\n";
}
} else {
// We may need to change the screen resolution. The code below is
// suboptimal; in the future, we probably want to only touch the CRTC
// that the window is on.
XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow ? _xwindow : x11_pipe->get_root());
SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
SizeID new_size_id = (SizeID) -1;
int num_sizes = 0;
XRRScreenSize *xrrs;
xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
for (int i = 0; i < num_sizes; ++i) {
if (xrrs[i].width == reqsizex &&
xrrs[i].height == reqsizey) {
new_size_id = i;
}
}
if (new_size_id == (SizeID) -1) {
x11display_cat.error()
<< "Videocard has no supported display resolutions at specified res ("
<< reqsizex << " x " << reqsizey << ")\n";
// Just go fullscreen at native resolution, then.
properties.set_origin(x, y);
properties.set_size(width, height);
} else {
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Switching to fullscreen with resolution "
<< reqsizex << "x" << reqsizey << "\n";
}
if (new_size_id != old_size_id) {
_XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
if (_orig_size_id == (SizeID) -1) {
// Remember the original resolution so we can switch back to it.
_orig_size_id = old_size_id;
}
// Since the above changes the entire screen configuration, we
// have to set the origin to 0, 0.
properties.set_origin(0, 0);
}
}
}
} else {
// Change the resolution back to what it was. Don't remove the SizeID
// typecast!
if (_orig_size_id != (SizeID) -1) {
XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
_XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), _orig_size_id, _orig_rotation, CurrentTime);
_orig_size_id = (SizeID) -1;
}
// Set the origin back to what it was
if (!properties.has_origin() && _properties.has_origin()) {
properties.set_origin(_properties.get_x_origin(), _properties.get_y_origin());
}
}
}
if (properties.has_origin()) {
// A coordinate of -2 means to center the window on screen.
if (properties.get_x_origin() == -2 || properties.get_y_origin() == -2) {
int x_origin = properties.get_x_origin();
int y_origin = properties.get_y_origin();
if (properties.has_size()) {
if (x_origin == -2) {
x_origin = 0.5 * (x11_pipe->get_display_width() - properties.get_x_size());
}
if (y_origin == -2) {
y_origin = 0.5 * (x11_pipe->get_display_height() - properties.get_y_size());
}
} else {
if (x_origin == -2) {
x_origin = 0.5 * (x11_pipe->get_display_width() - _properties.get_x_size());
}
if (y_origin == -2) {
y_origin = 0.5 * (x11_pipe->get_display_height() - _properties.get_y_size());
}
}
properties.set_origin(x_origin, y_origin);
}
}
GraphicsWindow::set_properties_now(properties);
if (!properties.is_any_specified()) {
// The base class has already handled this case.
return;
}
// The window is already open; we are limited to what we can change on the
// fly.
// We'll pass some property requests on as a window manager hint.
set_wm_properties(properties, true);
// The window title may be changed by issuing another hint request. Assume
// this will be honored.
if (properties.has_title()) {
_properties.set_title(properties.get_title());
properties.clear_title();
}
// Same for fullscreen.
if (properties.has_fullscreen()) {
_properties.set_fullscreen(properties.get_fullscreen());
properties.clear_fullscreen();
}
// Same for maximized.
if (properties.has_maximized()) {
_properties.set_maximized(properties.get_maximized());
properties.clear_maximized();
}
// The size and position of an already-open window are changed via explicit
// X calls. These may still get intercepted by the window manager. Rather
// than changing _properties immediately, we'll wait for the ConfigureNotify
// message to come back.
XWindowChanges changes;
int value_mask = 0;
if (_properties.get_fullscreen()) {
if (_properties.get_x_origin() != 0 ||
_properties.get_y_origin() != 0) {
changes.x = 0;
changes.y = 0;
value_mask |= CWX | CWY;
properties.clear_origin();
}
}
else if (properties.has_origin()) {
changes.x = properties.get_x_origin();
changes.y = properties.get_y_origin();
if (changes.x != -1) value_mask |= CWX;
if (changes.y != -1) value_mask |= CWY;
properties.clear_origin();
}
// This, too. But we can't currently change out of fixed_size mode.
if (properties.has_fixed_size() && properties.get_fixed_size()) {
_properties.set_fixed_size(properties.get_fixed_size());
properties.clear_fixed_size();
_fixed_size = _properties.get_size();
}
if (properties.has_size()) {
changes.width = properties.get_x_size();
changes.height = properties.get_y_size();
value_mask |= (CWWidth | CWHeight);
if (_properties.get_fixed_size()) {
_fixed_size = properties.get_size();
}
properties.clear_size();
}
if (properties.has_z_order()) {
// We'll send the classic stacking request through the standard interface,
// for users of primitive window managers; but we'll also send it as a
// window manager hint, for users of modern window managers.
_properties.set_z_order(properties.get_z_order());
switch (properties.get_z_order()) {
case WindowProperties::Z_bottom:
changes.stack_mode = Below;
break;
case WindowProperties::Z_normal:
changes.stack_mode = TopIf;
break;
case WindowProperties::Z_top:
changes.stack_mode = Above;
break;
}
value_mask |= (CWStackMode);
properties.clear_z_order();
}
// We hide the cursor by setting it to an invisible pixmap. We can also
// load a custom cursor from a file.
if (properties.has_cursor_hidden() || properties.has_cursor_filename()) {
if (properties.has_cursor_hidden()) {
_properties.set_cursor_hidden(properties.get_cursor_hidden());
properties.clear_cursor_hidden();
}
Filename cursor_filename;
if (properties.has_cursor_filename()) {
cursor_filename = properties.get_cursor_filename();
_properties.set_cursor_filename(cursor_filename);
properties.clear_cursor_filename();
}
Filename filename = properties.get_cursor_filename();
_properties.set_cursor_filename(filename);
if (_properties.get_cursor_hidden()) {
XDefineCursor(_display, _xwindow, x11_pipe->get_hidden_cursor());
} else if (!cursor_filename.empty()) {
// Note that if the cursor fails to load, cursor will be None
X11_Cursor cursor = get_cursor(cursor_filename);
XDefineCursor(_display, _xwindow, cursor);
} else {
XDefineCursor(_display, _xwindow, None);
}
// Regrab the mouse if we changed the cursor, otherwise it won't update.
if (!properties.has_mouse_mode() &&
_properties.get_mouse_mode() != WindowProperties::M_absolute) {
properties.set_mouse_mode(_properties.get_mouse_mode());
}
}
if (properties.has_foreground()) {
if (properties.get_foreground()) {
XSetInputFocus(_display, _xwindow, RevertToPointerRoot, CurrentTime);
} else {
XSetInputFocus(_display, PointerRoot, RevertToPointerRoot, CurrentTime);
}
properties.clear_foreground();
}
if (properties.has_mouse_mode()) {
switch (properties.get_mouse_mode()) {
case WindowProperties::M_absolute:
XUngrabPointer(_display, CurrentTime);
if (_dga_mouse_enabled) {
x11_pipe->disable_dga_mouse();
_dga_mouse_enabled = false;
}
if (_raw_mouse_enabled) {
x11_pipe->disable_raw_mouse();
_raw_mouse_enabled = false;
}
_properties.set_mouse_mode(WindowProperties::M_absolute);
properties.clear_mouse_mode();
break;
case WindowProperties::M_relative:
if (!_dga_mouse_enabled) {
if (x11_pipe->supports_relative_mouse()) {
X11_Cursor cursor = None;
if (_properties.get_cursor_hidden()) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
cursor = x11_pipe->get_hidden_cursor();
}
if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
x11display_cat.error() << "Failed to grab pointer!\n";
} else {
if (x11_pipe->enable_dga_mouse()) {
_dga_mouse_enabled = true;
} else {
_raw_mouse_enabled = _raw_mouse_enabled || x11_pipe->enable_raw_mouse();
}
_properties.set_mouse_mode(WindowProperties::M_relative);
properties.clear_mouse_mode();
// Get the real mouse position, so we can add/subtract our relative
// coordinates later.
XEvent event;
XQueryPointer(_display, _xwindow, &event.xbutton.root,
&event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root,
&event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
_input->set_pointer_in_window(event.xbutton.x, event.xbutton.y);
}
}
}
break;
case WindowProperties::M_confined:
{
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
if (_dga_mouse_enabled) {
x11_pipe->disable_dga_mouse();
_dga_mouse_enabled = false;
}
if (_raw_mouse_enabled) {
x11_pipe->disable_raw_mouse();
_raw_mouse_enabled = false;
}
X11_Cursor cursor = None;
if (_properties.get_cursor_hidden()) {
cursor = x11_pipe->get_hidden_cursor();
}
if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
x11display_cat.error() << "Failed to grab pointer!\n";
} else {
_properties.set_mouse_mode(WindowProperties::M_confined);
properties.clear_mouse_mode();
}
}
break;
}
}
if (value_mask != 0) {
// We must call this after changing the WM properties, otherwise we may
// get misleading ConfigureNotify events in the wrong order.
XReconfigureWMWindow(_display, _xwindow, _screen, value_mask, &changes);
// Don't draw anything until this is done reconfiguring.
_awaiting_configure = true;
}
}
/**
* Closes the window right now. Called from the window thread.
*/
void x11GraphicsWindow::
close_window() {
if (_gsg != nullptr) {
_gsg.clear();
}
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
if (_ic != (XIC)nullptr) {
XDestroyIC(_ic);
_ic = (XIC)nullptr;
}
if (_xwindow != (X11_Window)nullptr) {
XDestroyWindow(_display, _xwindow);
_xwindow = (X11_Window)nullptr;
// This may be necessary if we just closed the last X window in an
// application, so the server hears the close request.
XFlush(_display);
}
// Change the resolution back to what it was. Don't remove the SizeID
// typecast!
if (_orig_size_id != (SizeID) -1) {
X11_Window root;
if (_pipe != nullptr) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
root = x11_pipe->get_root();
} else {
// Oops. Looks like the pipe was destroyed before the window gets
// closed. Oh well, let's get the root window by ourselves.
root = RootWindow(_display, _screen);
}
XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, root);
_XRRSetScreenConfig(_display, conf, root, _orig_size_id, _orig_rotation, CurrentTime);
_orig_size_id = -1;
}
GraphicsWindow::close_window();
}
/**
* Opens the window right now. Called from the window thread. Returns true
* if the window is successfully opened, or false if there was a problem.
*/
bool x11GraphicsWindow::
open_window() {
if (_visual_info == nullptr) {
// No X visual for this fbconfig; how can we open the window?
x11display_cat.error()
<< "No X visual: cannot open window.\n";
return false;
}
x11GraphicsPipe *x11_pipe;
DCAST_INTO_R(x11_pipe, _pipe, false);
if (!_properties.has_origin()) {
_properties.set_origin(0, 0);
}
if (!_properties.has_size()) {
_properties.set_size(100, 100);
}
// Make sure we are not making X11 calls from other threads.
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
X11_Window parent_window = x11_pipe->get_root();
WindowHandle *window_handle = _properties.get_parent_window();
if (window_handle != nullptr) {
x11display_cat.info()
<< "Got parent_window " << *window_handle << "\n";
WindowHandle::OSHandle *os_handle = window_handle->get_os_handle();
if (os_handle != nullptr) {
x11display_cat.info()
<< "os_handle type " << os_handle->get_type() << "\n";
if (os_handle->is_of_type(NativeWindowHandle::X11Handle::get_class_type())) {
NativeWindowHandle::X11Handle *x11_handle = DCAST(NativeWindowHandle::X11Handle, os_handle);
parent_window = x11_handle->get_handle();
} else if (os_handle->is_of_type(NativeWindowHandle::IntHandle::get_class_type())) {
NativeWindowHandle::IntHandle *int_handle = DCAST(NativeWindowHandle::IntHandle, os_handle);
parent_window = (X11_Window)int_handle->get_handle();
}
}
}
_parent_window_handle = window_handle;
_event_mask =
ButtonPressMask | ButtonReleaseMask |
KeyPressMask | KeyReleaseMask |
EnterWindowMask | LeaveWindowMask |
PointerMotionMask |
FocusChangeMask | StructureNotifyMask |
PropertyChangeMask;
// Initialize window attributes
XSetWindowAttributes wa;
wa.background_pixel = XBlackPixel(_display, _screen);
wa.border_pixel = 0;
wa.colormap = _colormap;
wa.event_mask = _event_mask;
wa.override_redirect = _override_redirect;
unsigned long attrib_mask =
CWBackPixel | CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect;
_xwindow = XCreateWindow
(_display, parent_window,
_properties.get_x_origin(), _properties.get_y_origin(),
_properties.get_x_size(), _properties.get_y_size(),
0, _visual_info->depth, InputOutput,
_visual_info->visual, attrib_mask, &wa);
if (_xwindow == (X11_Window)0) {
x11display_cat.error()
<< "failed to create X window.\n";
return false;
}
if (_properties.get_fixed_size()) {
_fixed_size = _properties.get_size();
}
set_wm_properties(_properties, false);
// We don't specify any fancy properties of the XIC. It would be nicer if
// we could support fancy IM's that want preedit callbacks, etc., but that
// can wait until we have an X server that actually supports these to test
// it on.
XIM im = x11_pipe->get_im();
_ic = nullptr;
if (im) {
_ic = XCreateIC(im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, _xwindow, nullptr);
if (_ic == (XIC)nullptr) {
x11display_cat.warning()
<< "Couldn't create input context.\n";
}
}
if (_properties.get_cursor_hidden()) {
XDefineCursor(_display, _xwindow, x11_pipe->get_hidden_cursor());
} else if (_properties.has_cursor_filename() && !_properties.get_cursor_filename().empty()) {
// Note that if the cursor fails to load, cursor will be None
X11_Cursor cursor = get_cursor(_properties.get_cursor_filename());
XDefineCursor(_display, _xwindow, cursor);
}
XMapWindow(_display, _xwindow);
if (_properties.get_raw_mice()) {
open_raw_mice();
} else {
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Raw mice not requested.\n";
}
}
// Create a WindowHandle for ourselves
_window_handle = NativeWindowHandle::make_x11(_xwindow);
// And tell our parent window that we're now its child.
if (_parent_window_handle != nullptr) {
_parent_window_handle->attach_child(_window_handle);
}
return true;
}
/**
* Asks the window manager to set the appropriate properties. In X, these
* properties cannot be specified directly by the application; they must be
* requested via the window manager, which may or may not choose to honor the
* request.
*
* If already_mapped is true, the window has already been mapped (manifested)
* on the display. This means we may need to use a different action in some
* cases.
*
* Assumes the X11 lock is held.
*/
void x11GraphicsWindow::
set_wm_properties(const WindowProperties &properties, bool already_mapped) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
// Name the window if there is a name
XTextProperty window_name;
XTextProperty *window_name_p = nullptr;
if (properties.has_title()) {
const char *name = properties.get_title().c_str();
if (XStringListToTextProperty((char **)&name, 1, &window_name) != 0) {
window_name_p = &window_name;
}
}
// The size hints request a window of a particular size andor a particular
// placement onscreen.
XSizeHints *size_hints_p = nullptr;
if (properties.has_origin() || properties.has_size()) {
size_hints_p = XAllocSizeHints();
if (size_hints_p != nullptr) {
if (properties.has_origin()) {
size_hints_p->x = properties.get_x_origin();
size_hints_p->y = properties.get_y_origin();
size_hints_p->flags |= USPosition;
}
LVecBase2i size = _properties.get_size();
if (properties.has_size()) {
size = properties.get_size();
size_hints_p->width = size.get_x();
size_hints_p->height = size.get_y();
size_hints_p->flags |= USSize;
}
if (properties.get_fixed_size()) {
size_hints_p->min_width = size.get_x();
size_hints_p->min_height = size.get_y();
size_hints_p->max_width = size.get_x();
size_hints_p->max_height = size.get_y();
size_hints_p->flags |= (PMinSize | PMaxSize);
}
}
}
// The window manager hints include requests to the window manager other
// than those specific to window geometry.
XWMHints *wm_hints_p = nullptr;
wm_hints_p = XAllocWMHints();
if (wm_hints_p != nullptr) {
if (properties.has_minimized() && properties.get_minimized()) {
wm_hints_p->initial_state = IconicState;
} else {
wm_hints_p->initial_state = NormalState;
}
wm_hints_p->flags = StateHint;
}
// Two competing window manager interfaces have evolved. One of them allows
// to set certain properties as a "type"; the other one as a "state". We'll
// try to honor both.
static const int max_type_data = 32;
int32_t type_data[max_type_data];
int next_type_data = 0;
static const int max_state_data = 32;
int32_t state_data[max_state_data];
int next_state_data = 0;
static const int max_set_data = 32;
class SetAction {
public:
inline SetAction() { }
inline SetAction(Atom state, Atom action) : _state(state), _action(action) { }
Atom _state;
Atom _action;
};
SetAction set_data[max_set_data];
int next_set_data = 0;
if (properties.has_maximized()) {
if (properties.get_maximized()) {
state_data[next_state_data++] = x11_pipe->_net_wm_state_maximized_vert;
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_maximized_vert, 1);
state_data[next_state_data++] = x11_pipe->_net_wm_state_maximized_horz;
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_maximized_horz, 1);
} else {
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_maximized_vert, 0);
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_maximized_horz, 0);
}
}
if (properties.has_fullscreen()) {
if (properties.get_fullscreen()) {
// For a "fullscreen" request, we pass this through, hoping the window
// manager will support EWMH.
type_data[next_type_data++] = x11_pipe->_net_wm_window_type_fullscreen;
// We also request it as a state.
state_data[next_state_data++] = x11_pipe->_net_wm_state_fullscreen;
// Don't ask me why this has to be 10 and not _net_wm_state_add. It
// doesn't seem to work otherwise.
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_fullscreen, 1);
} else {
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_fullscreen, 0);
}
}
// If we asked for a window without a border, there's no excellent way to
// arrange that. For users whose window managers follow the EWMH
// specification, we can ask for a "splash" screen, which is usually
// undecorated. It's not exactly right, but the spec doesn't give us an
// exactly-right option.
// For other users, we'll totally punt and just set the window's Class to
// "Undecorated", and let the user configure hisher window manager not to
// put a border around windows of this class.
XClassHint *class_hints_p = nullptr;
if (!x_wm_class.empty()) {
// Unless the user wanted to use his own WM_CLASS, of course.
class_hints_p = XAllocClassHint();
class_hints_p->res_class = (char*) x_wm_class.c_str();
if (!x_wm_class_name.empty()) {
class_hints_p->res_name = (char*) x_wm_class_name.c_str();
}
} else if (properties.get_undecorated() || properties.get_fullscreen()) {
class_hints_p = XAllocClassHint();
class_hints_p->res_class = (char*) "Undecorated";
}
if (properties.get_undecorated() && !properties.get_fullscreen()) {
type_data[next_type_data++] = x11_pipe->_net_wm_window_type_splash;
}
if (properties.has_z_order()) {
switch (properties.get_z_order()) {
case WindowProperties::Z_bottom:
state_data[next_state_data++] = x11_pipe->_net_wm_state_below;
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
x11_pipe->_net_wm_state_add);
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
x11_pipe->_net_wm_state_remove);
break;
case WindowProperties::Z_normal:
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
x11_pipe->_net_wm_state_remove);
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
x11_pipe->_net_wm_state_remove);
break;
case WindowProperties::Z_top:
state_data[next_state_data++] = x11_pipe->_net_wm_state_above;
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
x11_pipe->_net_wm_state_remove);
set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
x11_pipe->_net_wm_state_add);
break;
}
}
nassertv(next_type_data < max_type_data);
nassertv(next_state_data < max_state_data);
nassertv(next_set_data < max_set_data);
// Add the process ID as a convenience for other applications.
int32_t pid = getpid();
XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_pid,
XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)&pid, 1);
// Disable compositing effects in fullscreen mode.
if (properties.has_fullscreen()) {
int32_t compositor = properties.get_fullscreen() ? 1 : 0;
XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_bypass_compositor,
XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)&compositor, 1);
}
XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_window_type,
XA_ATOM, 32, PropModeReplace,
(unsigned char *)type_data, next_type_data);
// Request the state properties all at once.
XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_state,
XA_ATOM, 32, PropModeReplace,
(unsigned char *)state_data, next_state_data);
if (already_mapped) {
// We have to request state changes differently when the window has been
// mapped. To do this, we need to send a client message to the root
// window for each change.
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
for (int i = 0; i < next_set_data; ++i) {
XClientMessageEvent event;
memset(&event, 0, sizeof(event));
event.type = ClientMessage;
event.send_event = True;
event.display = _display;
event.window = _xwindow;
event.message_type = x11_pipe->_net_wm_state;
event.format = 32;
event.data.l[0] = set_data[i]._action;
event.data.l[1] = set_data[i]._state;
event.data.l[2] = 0;
event.data.l[3] = 1;
XSendEvent(_display, x11_pipe->get_root(), True, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent *)&event);
}
}
XSetWMProperties(_display, _xwindow, window_name_p, window_name_p,
nullptr, 0, size_hints_p, wm_hints_p, class_hints_p);
if (size_hints_p != nullptr) {
XFree(size_hints_p);
}
if (wm_hints_p != nullptr) {
XFree(wm_hints_p);
}
if (class_hints_p != nullptr) {
XFree(class_hints_p);
}
// Also, indicate to the window manager that we'd like to get a chance to
// close our windows cleanly, rather than being rudely disconnected from the
// X server if the user requests a window close.
Atom protocols[] = {
_wm_delete_window,
};
XSetWMProtocols(_display, _xwindow, protocols,
sizeof(protocols) / sizeof(Atom));
}
/**
* Allocates a colormap appropriate to the visual and stores in in the
* _colormap method.
*/
void x11GraphicsWindow::
setup_colormap(XVisualInfo *visual) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_V(x11_pipe, _pipe);
X11_Window root_window = x11_pipe->get_root();
_colormap = XCreateColormap(_display, root_window,
visual->visual, AllocNone);
}
/**
* Adds raw mice to the _input_devices list.
* @deprecated obtain raw devices via the device manager instead.
*/
void x11GraphicsWindow::
open_raw_mice() {
#ifdef PHAVE_LINUX_INPUT_H
bool any_present = false;
bool any_mice = false;
for (int i=0; i<64; i++) {
ostringstream fnb;
fnb << "/dev/input/event" << i;
string fn = fnb.str();
int fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK, 0);
if (fd >= 0) {
EvdevInputDevice *device = new EvdevInputDevice(nullptr, fd);
nassertd(device != NULL) continue;
if (device->has_pointer()) {
add_input_device(device);
x11display_cat.info()
<< "Raw mouse " << _input_devices.size()
<< " detected: " << device->get_name() << "\n";
any_mice = true;
any_present = true;
}
} else {
if (errno == ENOENT || errno == ENOTDIR) {
break;
} else {
any_present = true;
x11display_cat.error()
<< "Opening raw mice: " << strerror(errno) << " " << fn << "\n";
}
}
}
if (any_mice) {
_properties.set_raw_mice(true);
} else if (!any_present) {
x11display_cat.error() <<
"Opening raw mice: files not found: /dev/input/event*\n";
} else {
x11display_cat.error() <<
"Opening raw mice: no mouse devices detected in /dev/input/event*\n";
}
#else
x11display_cat.error() <<
"Opening raw mice: panda not compiled with raw mouse support.\n";
#endif
}
/**
* Generates a keystroke corresponding to the indicated X KeyPress event.
*/
void x11GraphicsWindow::
handle_keystroke(XKeyEvent &event) {
if (_properties.get_mouse_mode() != WindowProperties::M_relative) {
_input->set_pointer_in_window(event.x, event.y);
}
if (_ic) {
// First, get the keystroke as a wide-character sequence.
static const int buffer_size = 256;
wchar_t buffer[buffer_size];
Status status;
int len = XwcLookupString(_ic, &event, buffer, buffer_size, nullptr,
&status);
if (status == XBufferOverflow) {
x11display_cat.error()
<< "Overflowed input buffer.\n";
}
// Now each of the returned wide characters represents a keystroke.
for (int i = 0; i < len; i++) {
_input->keystroke(buffer[i]);
}
} else {
// Without an input context, just get the ascii keypress.
ButtonHandle button = get_button(event, true);
if (button.has_ascii_equivalent()) {
_input->keystroke(button.get_ascii_equivalent());
}
}
}
/**
* Generates a keypress corresponding to the indicated X KeyPress event.
*/
void x11GraphicsWindow::
handle_keypress(XKeyEvent &event) {
if (_properties.get_mouse_mode() != WindowProperties::M_relative) {
_input->set_pointer_in_window(event.x, event.y);
}
// Now get the raw unshifted button.
ButtonHandle button = get_button(event, false);
if (button != ButtonHandle::none()) {
if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) {
_input->button_down(KeyboardButton::control());
}
if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) {
_input->button_down(KeyboardButton::shift());
}
if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) {
_input->button_down(KeyboardButton::alt());
}
if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) {
_input->button_down(KeyboardButton::meta());
}
_input->button_down(button);
}
}
/**
* Generates a keyrelease corresponding to the indicated X KeyRelease event.
*/
void x11GraphicsWindow::
handle_keyrelease(XKeyEvent &event) {
if (_properties.get_mouse_mode() != WindowProperties::M_relative) {
_input->set_pointer_in_window(event.x, event.y);
}
// Now get the raw unshifted button.
ButtonHandle button = get_button(event, false);
if (button != ButtonHandle::none()) {
if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) {
_input->button_up(KeyboardButton::control());
}
if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) {
_input->button_up(KeyboardButton::shift());
}
if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) {
_input->button_up(KeyboardButton::alt());
}
if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) {
_input->button_up(KeyboardButton::meta());
}
_input->button_up(button);
}
}
/**
* Returns the Panda ButtonHandle corresponding to the keyboard button
* indicated by the given key event.
*/
ButtonHandle x11GraphicsWindow::
get_button(XKeyEvent &key_event, bool allow_shift) {
KeySym key = XLookupKeysym(&key_event, 0);
if ((key_event.state & Mod2Mask) != 0) {
// Mod2Mask corresponds to NumLock being in effect. In this case, we want
// to get the alternate keysym associated with any keypad keys. Weird
// system.
KeySym k2;
ButtonHandle button;
switch (key) {
case XK_KP_Space:
case XK_KP_Tab:
case XK_KP_Enter:
case XK_KP_F1:
case XK_KP_F2:
case XK_KP_F3:
case XK_KP_F4:
case XK_KP_Equal:
case XK_KP_Multiply:
case XK_KP_Add:
case XK_KP_Separator:
case XK_KP_Subtract:
case XK_KP_Divide:
case XK_KP_Left:
case XK_KP_Up:
case XK_KP_Right:
case XK_KP_Down:
case XK_KP_Begin:
case XK_KP_Prior:
case XK_KP_Next:
case XK_KP_Home:
case XK_KP_End:
case XK_KP_Insert:
case XK_KP_Delete:
case XK_KP_0:
case XK_KP_1:
case XK_KP_2:
case XK_KP_3:
case XK_KP_4:
case XK_KP_5:
case XK_KP_6:
case XK_KP_7:
case XK_KP_8:
case XK_KP_9:
k2 = XLookupKeysym(&key_event, 1);
button = map_button(k2);
if (button != ButtonHandle::none()) {
return button;
}
// If that didn't produce a button we know, just fall through and handle
// the normal, un-numlocked key.
break;
default:
break;
}
}
if (allow_shift) {
// If shift is held down, get the shifted keysym.
if ((key_event.state & ShiftMask) != 0) {
KeySym k2 = XLookupKeysym(&key_event, 1);
ButtonHandle button = map_button(k2);
if (button != ButtonHandle::none()) {
return button;
}
}
// If caps lock is down, shift lowercase letters to uppercase. We can do
// this in just the ASCII set, because we handle international keyboards
// elsewhere (via an input context).
if ((key_event.state & (ShiftMask | LockMask)) != 0) {
if (key >= XK_a && key <= XK_z) {
key += (XK_A - XK_a);
}
}
}
return map_button(key);
}
/**
* Maps from a single X keysym to Panda's ButtonHandle. Called by
* get_button(), above.
*/
ButtonHandle x11GraphicsWindow::
map_button(KeySym key) const {
switch (key) {
case NoSymbol:
return ButtonHandle::none();
case XK_BackSpace:
return KeyboardButton::backspace();
case XK_Tab:
case XK_KP_Tab:
return KeyboardButton::tab();
case XK_Return:
case XK_KP_Enter:
return KeyboardButton::enter();
case XK_Escape:
return KeyboardButton::escape();
case XK_KP_Space:
case XK_space:
return KeyboardButton::space();
case XK_exclam:
return KeyboardButton::ascii_key('!');
case XK_quotedbl:
return KeyboardButton::ascii_key('"');
case XK_numbersign:
return KeyboardButton::ascii_key('#');
case XK_dollar:
return KeyboardButton::ascii_key('$');
case XK_percent:
return KeyboardButton::ascii_key('%');
case XK_ampersand:
return KeyboardButton::ascii_key('&');
case XK_apostrophe: // == XK_quoteright
case XK_dead_acute: // on int'l keyboards
return KeyboardButton::ascii_key('\'');
case XK_parenleft:
return KeyboardButton::ascii_key('(');
case XK_parenright:
return KeyboardButton::ascii_key(')');
case XK_asterisk:
case XK_KP_Multiply:
return KeyboardButton::ascii_key('*');
case XK_plus:
case XK_KP_Add:
return KeyboardButton::ascii_key('+');
case XK_comma:
case XK_KP_Separator:
return KeyboardButton::ascii_key(',');
case XK_minus:
case XK_KP_Subtract:
return KeyboardButton::ascii_key('-');
case XK_period:
case XK_KP_Decimal:
return KeyboardButton::ascii_key('.');
case XK_slash:
case XK_KP_Divide:
return KeyboardButton::ascii_key('/');
case XK_0:
case XK_KP_0:
return KeyboardButton::ascii_key('0');
case XK_1:
case XK_KP_1:
return KeyboardButton::ascii_key('1');
case XK_2:
case XK_KP_2:
return KeyboardButton::ascii_key('2');
case XK_3:
case XK_KP_3:
return KeyboardButton::ascii_key('3');
case XK_4:
case XK_KP_4:
return KeyboardButton::ascii_key('4');
case XK_5:
case XK_KP_5:
return KeyboardButton::ascii_key('5');
case XK_6:
case XK_KP_6:
return KeyboardButton::ascii_key('6');
case XK_7:
case XK_KP_7:
return KeyboardButton::ascii_key('7');
case XK_8:
case XK_KP_8:
return KeyboardButton::ascii_key('8');
case XK_9:
case XK_KP_9:
return KeyboardButton::ascii_key('9');
case XK_colon:
return KeyboardButton::ascii_key(':');
case XK_semicolon:
return KeyboardButton::ascii_key(';');
case XK_less:
return KeyboardButton::ascii_key('<');
case XK_equal:
case XK_KP_Equal:
return KeyboardButton::ascii_key('=');
case XK_greater:
return KeyboardButton::ascii_key('>');
case XK_question:
return KeyboardButton::ascii_key('?');
case XK_at:
return KeyboardButton::ascii_key('@');
case XK_A:
return KeyboardButton::ascii_key('A');
case XK_B:
return KeyboardButton::ascii_key('B');
case XK_C:
return KeyboardButton::ascii_key('C');
case XK_D:
return KeyboardButton::ascii_key('D');
case XK_E:
return KeyboardButton::ascii_key('E');
case XK_F:
return KeyboardButton::ascii_key('F');
case XK_G:
return KeyboardButton::ascii_key('G');
case XK_H:
return KeyboardButton::ascii_key('H');
case XK_I:
return KeyboardButton::ascii_key('I');
case XK_J:
return KeyboardButton::ascii_key('J');
case XK_K:
return KeyboardButton::ascii_key('K');
case XK_L:
return KeyboardButton::ascii_key('L');
case XK_M:
return KeyboardButton::ascii_key('M');
case XK_N:
return KeyboardButton::ascii_key('N');
case XK_O:
return KeyboardButton::ascii_key('O');
case XK_P:
return KeyboardButton::ascii_key('P');
case XK_Q:
return KeyboardButton::ascii_key('Q');
case XK_R:
return KeyboardButton::ascii_key('R');
case XK_S:
return KeyboardButton::ascii_key('S');
case XK_T:
return KeyboardButton::ascii_key('T');
case XK_U:
return KeyboardButton::ascii_key('U');
case XK_V:
return KeyboardButton::ascii_key('V');
case XK_W:
return KeyboardButton::ascii_key('W');
case XK_X:
return KeyboardButton::ascii_key('X');
case XK_Y:
return KeyboardButton::ascii_key('Y');
case XK_Z:
return KeyboardButton::ascii_key('Z');
case XK_bracketleft:
return KeyboardButton::ascii_key('[');
case XK_backslash:
return KeyboardButton::ascii_key('\\');
case XK_bracketright:
return KeyboardButton::ascii_key(']');
case XK_asciicircum:
return KeyboardButton::ascii_key('^');
case XK_underscore:
return KeyboardButton::ascii_key('_');
case XK_grave: // == XK_quoteleft
case XK_dead_grave: // on int'l keyboards
return KeyboardButton::ascii_key('`');
case XK_a:
return KeyboardButton::ascii_key('a');
case XK_b:
return KeyboardButton::ascii_key('b');
case XK_c:
return KeyboardButton::ascii_key('c');
case XK_d:
return KeyboardButton::ascii_key('d');
case XK_e:
return KeyboardButton::ascii_key('e');
case XK_f:
return KeyboardButton::ascii_key('f');
case XK_g:
return KeyboardButton::ascii_key('g');
case XK_h:
return KeyboardButton::ascii_key('h');
case XK_i:
return KeyboardButton::ascii_key('i');
case XK_j:
return KeyboardButton::ascii_key('j');
case XK_k:
return KeyboardButton::ascii_key('k');
case XK_l:
return KeyboardButton::ascii_key('l');
case XK_m:
return KeyboardButton::ascii_key('m');
case XK_n:
return KeyboardButton::ascii_key('n');
case XK_o:
return KeyboardButton::ascii_key('o');
case XK_p:
return KeyboardButton::ascii_key('p');
case XK_q:
return KeyboardButton::ascii_key('q');
case XK_r:
return KeyboardButton::ascii_key('r');
case XK_s:
return KeyboardButton::ascii_key('s');
case XK_t:
return KeyboardButton::ascii_key('t');
case XK_u:
return KeyboardButton::ascii_key('u');
case XK_v:
return KeyboardButton::ascii_key('v');
case XK_w:
return KeyboardButton::ascii_key('w');
case XK_x:
return KeyboardButton::ascii_key('x');
case XK_y:
return KeyboardButton::ascii_key('y');
case XK_z:
return KeyboardButton::ascii_key('z');
case XK_braceleft:
return KeyboardButton::ascii_key('{');
case XK_bar:
return KeyboardButton::ascii_key('|');
case XK_braceright:
return KeyboardButton::ascii_key('}');
case XK_asciitilde:
return KeyboardButton::ascii_key('~');
case XK_F1:
case XK_KP_F1:
return KeyboardButton::f1();
case XK_F2:
case XK_KP_F2:
return KeyboardButton::f2();
case XK_F3:
case XK_KP_F3:
return KeyboardButton::f3();
case XK_F4:
case XK_KP_F4:
return KeyboardButton::f4();
case XK_F5:
return KeyboardButton::f5();
case XK_F6:
return KeyboardButton::f6();
case XK_F7:
return KeyboardButton::f7();
case XK_F8:
return KeyboardButton::f8();
case XK_F9:
return KeyboardButton::f9();
case XK_F10:
return KeyboardButton::f10();
case XK_F11:
return KeyboardButton::f11();
case XK_F12:
return KeyboardButton::f12();
case XK_KP_Left:
case XK_Left:
return KeyboardButton::left();
case XK_KP_Up:
case XK_Up:
return KeyboardButton::up();
case XK_KP_Right:
case XK_Right:
return KeyboardButton::right();
case XK_KP_Down:
case XK_Down:
return KeyboardButton::down();
case XK_KP_Prior:
case XK_Prior:
return KeyboardButton::page_up();
case XK_KP_Next:
case XK_Next:
return KeyboardButton::page_down();
case XK_KP_Home:
case XK_Home:
return KeyboardButton::home();
case XK_KP_End:
case XK_End:
return KeyboardButton::end();
case XK_KP_Insert:
case XK_Insert:
return KeyboardButton::insert();
case XK_KP_Delete:
case XK_Delete:
return KeyboardButton::del();
case XK_Num_Lock:
return KeyboardButton::num_lock();
case XK_Scroll_Lock:
return KeyboardButton::scroll_lock();
case XK_Print:
return KeyboardButton::print_screen();
case XK_Pause:
return KeyboardButton::pause();
case XK_Menu:
return KeyboardButton::menu();
case XK_Shift_L:
return KeyboardButton::lshift();
case XK_Shift_R:
return KeyboardButton::rshift();
case XK_Control_L:
return KeyboardButton::lcontrol();
case XK_Control_R:
return KeyboardButton::rcontrol();
case XK_Alt_L:
return KeyboardButton::lalt();
case XK_Alt_R:
return KeyboardButton::ralt();
case XK_Meta_L:
case XK_Super_L:
return KeyboardButton::lmeta();
case XK_Meta_R:
case XK_Super_R:
return KeyboardButton::rmeta();
case XK_Caps_Lock:
return KeyboardButton::caps_lock();
case XK_Shift_Lock:
return KeyboardButton::shift_lock();
}
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Unrecognized keysym 0x" << std::hex << key << std::dec << "\n";
}
return ButtonHandle::none();
}
/**
* Maps from a single X keycode to Panda's ButtonHandle.
*/
ButtonHandle x11GraphicsWindow::
map_raw_button(KeyCode key) const {
#ifdef PHAVE_LINUX_INPUT_H
// Most X11 servers are configured to use the evdev driver, which
// adds 8 to the underlying evdev keycodes (not sure why).
// In any case, this means we can use the same mapping as our raw
// input code, which uses evdev directly.
int index = key - 8;
if (index > 0 && index < 128) {
return EvdevInputDevice::map_button(index);
}
#endif
return ButtonHandle::none();
}
/**
* Returns the Panda ButtonHandle corresponding to the mouse button indicated
* by the given button event.
*/
ButtonHandle x11GraphicsWindow::
get_mouse_button(XButtonEvent &button_event) {
int index = button_event.button;
if (index == x_wheel_up_button) {
return MouseButton::wheel_up();
} else if (index == x_wheel_down_button) {
return MouseButton::wheel_down();
} else if (index == x_wheel_left_button) {
return MouseButton::wheel_left();
} else if (index == x_wheel_right_button) {
return MouseButton::wheel_right();
} else {
return MouseButton::button(index - 1);
}
}
/**
* Returns a ButtonMap containing the association between raw buttons and
* virtual buttons.
*/
ButtonMap *x11GraphicsWindow::
get_keyboard_map() const {
// NB. This could be improved by using the Xkb API. XkbDescPtr desc =
// XkbGetMap(_display, XkbAllMapComponentsMask, XkbUseCoreKbd);
ButtonMap *map = new ButtonMap;
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
for (int k = 9; k <= 135; ++k) {
if (k >= 78 && k <= 91) {
// Ignore numpad keys for now. These are not mapped to separate button
// handles in Panda, so we don't want their mappings to conflict with
// the regular numeric keys.
continue;
}
ButtonHandle raw_button = map_raw_button(k);
if (raw_button == ButtonHandle::none()) {
continue;
}
KeySym sym = XkbKeycodeToKeysym(_display, k, 0, 0);
ButtonHandle button = map_button(sym);
std::string label;
// Compose a label for some keys; I have not yet been able to find an API
// that does this effectively.
if (sym >= XK_exclam && sym <= XK_asciitilde) {
label = toupper((char)sym);
}
else if (sym >= XK_F1 && sym <= XK_F35) {
label = "F" + format_string(sym - XK_F1 + 1);
}
else if (sym > 0x1000000 && sym < 0x1110000) {
// Unicode code point. Encode as UTF-8.
char32_t ch = sym & 0x0ffffff;
if ((ch & ~0x7f) == 0) {
label = string(1, (char)ch);
}
else if ((ch & ~0x7ff) == 0) {
label =
string(1, (char)((ch >> 6) | 0xc0)) +
string(1, (char)((ch & 0x3f) | 0x80));
}
else if ((ch & ~0xffff) == 0) {
label =
string(1, (char)((ch >> 12) | 0xe0)) +
string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) +
string(1, (char)((ch & 0x3f) | 0x80));
}
else {
label =
string(1, (char)((ch >> 18) | 0xf0)) +
string(1, (char)(((ch >> 12) & 0x3f) | 0x80)) +
string(1, (char)(((ch >> 6) & 0x3f) | 0x80)) +
string(1, (char)((ch & 0x3f) | 0x80));
}
}
else if ((sym >= XK_exclamdown && sym <= XK_umacron)
|| (sym >= XK_OE && sym <= XK_Ydiaeresis)
|| (sym >= XK_Serbian_dje && sym <= XK_Cyrillic_HARDSIGN)
|| (sym >= XK_kana_fullstop && sym <= XK_semivoicedsound)
|| (sym >= XK_Arabic_comma && sym <= XK_Arabic_sukun)
|| (sym >= XK_Greek_ALPHAaccent && sym <= XK_Greek_omega)
|| (sym >= XK_hebrew_doublelowline && sym <= XK_hebrew_taw)
|| (sym >= XK_Thai_kokai && sym <= XK_Thai_lekkao)
|| (sym >= XK_Hangul_Kiyeog && sym <= XK_Hangul_J_YeorinHieuh)
|| sym == XK_EuroSign
|| sym == XK_Korean_Won) {
// A non-unicode-based keysym. Translate this to the label.
char buffer[255];
int nbytes = XkbTranslateKeySym(_display, &sym, 0, buffer, 255, 0);
if (nbytes > 0) {
label.assign(buffer, nbytes);
}
}
if (button == ButtonHandle::none() && label.empty()) {
// No label and no mapping; this is useless.
continue;
}
map->map_button(raw_button, button, label);
}
return map;
}
/**
* This function is used as a predicate to XCheckIfEvent() to determine if the
* indicated queued X event is relevant and should be returned to this window.
*/
Bool x11GraphicsWindow::
check_event(X11_Display *display, XEvent *event, char *arg) {
const x11GraphicsWindow *self = (x11GraphicsWindow *)arg;
// We accept any event that is sent to our window. However, we have to let
// raw mouse events through, since they're not associated with any window.
return (event->xany.window == self->_xwindow ||
(event->type == GenericEvent && self->_raw_mouse_enabled));
}
/**
* Loads and returns a Cursor corresponding to the indicated filename. If the
* file cannot be loaded, returns None.
*/
X11_Cursor x11GraphicsWindow::
get_cursor(const Filename &filename) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_R(x11_pipe, _pipe, None);
if (x11_pipe->_xcursor_size == -1) {
x11display_cat.info()
<< "libXcursor.so.1 not available; cannot change mouse cursor.\n";
return None;
}
// First, look for the unresolved filename in our index.
pmap<Filename, X11_Cursor>::iterator fi = _cursor_filenames.find(filename);
if (fi != _cursor_filenames.end()) {
return fi->second;
}
// If it wasn't found, resolve the filename and search for that.
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
Filename resolved (filename);
if (!vfs->resolve_filename(resolved, get_model_path())) {
// The filename doesn't exist.
x11display_cat.warning()
<< "Could not find cursor filename " << filename << "\n";
return None;
}
fi = _cursor_filenames.find(resolved);
if (fi != _cursor_filenames.end()) {
return fi->second;
}
// Open the file through the virtual file system.
istream *str = vfs->open_read_file(resolved, true);
if (str == nullptr) {
x11display_cat.warning()
<< "Could not open cursor file " << filename << "\n";
return None;
}
// Check the first four bytes to see what kind of file it is.
char magic[4];
str->read(magic, 4);
if (!str->good()) {
x11display_cat.warning()
<< "Could not read from cursor file " << filename << "\n";
return None;
}
// Put back the read bytes. Do not use seekg, because this will
// corrupt the stream if it points to encrypted/compressed file
str->putback(magic[3]);
str->putback(magic[2]);
str->putback(magic[1]);
str->putback(magic[0]);
X11_Cursor h = None;
if (memcmp(magic, "Xcur", 4) == 0) {
// X11 cursor.
x11display_cat.debug()
<< "Loading X11 cursor " << filename << "\n";
XcursorFile xcfile;
xcfile.closure = str;
xcfile.read = &xcursor_read;
xcfile.write = &xcursor_write;
xcfile.seek = &xcursor_seek;
XcursorImages *images = x11_pipe->_XcursorXcFileLoadImages(&xcfile, x11_pipe->_xcursor_size);
if (images != nullptr) {
h = x11_pipe->_XcursorImagesLoadCursor(_display, images);
x11_pipe->_XcursorImagesDestroy(images);
}
} else if (memcmp(magic, "\0\0\1\0", 4) == 0
|| memcmp(magic, "\0\0\2\0", 4) == 0) {
// Windows .ico or .cur file.
x11display_cat.debug()
<< "Loading Windows cursor " << filename << "\n";
h = read_ico(*str);
}
// Delete the istream.
vfs->close_read_file(str);
if (h == None) {
x11display_cat.warning()
<< "X11 cursor filename '" << resolved << "' could not be loaded!\n";
}
_cursor_filenames[resolved] = h;
return h;
}
/**
* Reads a Windows .ico or .cur file from the indicated stream and returns it
* as an X11 Cursor. If the file cannot be loaded, returns None.
*/
X11_Cursor x11GraphicsWindow::
read_ico(istream &ico) {
x11GraphicsPipe *x11_pipe;
DCAST_INTO_R(x11_pipe, _pipe, None);
// Local structs, this is just POD, make input easier
typedef struct {
uint16_t reserved, type, count;
} IcoHeader;
typedef struct {
uint8_t width, height, colorCount, reserved;
uint16_t xhot, yhot;
uint32_t bitmapSize, offset;
} IcoEntry;
typedef struct {
uint32_t headerSize, width, height;
uint16_t planes, bitsPerPixel;
uint32_t compression, imageSize, xPixelsPerM, yPixelsPerM, colorsUsed, colorsImportant;
} IcoInfoHeader;
typedef struct {
uint8_t blue, green, red, reserved;
} IcoColor;
int i, entry = 0;
unsigned int j, k, mask, shift;
size_t colorCount, bitsPerPixel;
IcoHeader header;
IcoInfoHeader infoHeader;
IcoEntry *entries = nullptr;
IcoColor color, *palette = nullptr;
size_t xorBmpSize, andBmpSize;
char *curXor, *curAnd;
char *xorBmp = nullptr, *andBmp = nullptr;
XcursorImage *image = nullptr;
X11_Cursor ret = None;
int def_size = x11_pipe->_xcursor_size;
// Get our header, note that ICO = type 1 and CUR = type 2.
ico.read(reinterpret_cast<char *>(&header), sizeof(IcoHeader));
if (!ico.good()) goto cleanup;
if (header.type != 1 && header.type != 2) goto cleanup;
if (header.count < 1) goto cleanup;
// Read the entry table into memory, select the largest entry.
entries = new IcoEntry[header.count];
ico.read(reinterpret_cast<char *>(entries), header.count * sizeof(IcoEntry));
if (!ico.good()) goto cleanup;
for (i = 1; i < header.count; i++) {
if (entries[i].width == def_size && entries[i].height == def_size) {
// Wait, this is the default cursor size. This is perfect.
entry = i;
break;
}
if (entries[i].width > entries[entry].width ||
entries[i].height > entries[entry].height)
entry = i;
}
// Seek to the image in the ICO.
ico.seekg(entries[entry].offset);
if (!ico.good()) goto cleanup;
if (ico.peek() == 0x89) {
// Hang on, this is actually a PNG header.
PNMImage img;
PNMFileTypeRegistry *reg = PNMFileTypeRegistry::get_global_ptr();
if (!img.read(ico, "", reg->get_type_from_extension("png"))) {
goto cleanup;
}
img.set_maxval(255);
image = x11_pipe->_XcursorImageCreate(img.get_x_size(), img.get_y_size());
xel *ptr = img.get_array();
xelval *alpha = img.get_alpha_array();
size_t num_pixels = (size_t)img.get_x_size() * (size_t)img.get_y_size();
unsigned int *dest = image->pixels;
if (alpha != nullptr) {
for (size_t p = 0; p < num_pixels; ++p) {
*dest++ = (*alpha << 24U) | (ptr->r << 16U) | (ptr->g << 8U) | (ptr->b);
++ptr;
++alpha;
}
} else {
for (size_t p = 0; p < num_pixels; ++p) {
*dest++ = 0xff000000U | (ptr->r << 16U) | (ptr->g << 8U) | (ptr->b);
++ptr;
}
}
} else {
ico.read(reinterpret_cast<char *>(&infoHeader), sizeof(IcoInfoHeader));
if (!ico.good()) goto cleanup;
bitsPerPixel = infoHeader.bitsPerPixel;
if (infoHeader.compression != 0) goto cleanup;
// Load the color palette, if one exists.
if (bitsPerPixel != 24 && bitsPerPixel != 32) {
colorCount = 1 << bitsPerPixel;
palette = new IcoColor[colorCount];
ico.read(reinterpret_cast<char *>(palette), colorCount * sizeof(IcoColor));
if (!ico.good()) goto cleanup;
}
int and_stride = ((infoHeader.width >> 3) + 3) & ~0x03;
// Read in the pixel data.
xorBmpSize = (infoHeader.width * (infoHeader.height / 2) * bitsPerPixel) / 8;
andBmpSize = and_stride * (infoHeader.height / 2);
curXor = xorBmp = new char[xorBmpSize];
curAnd = andBmp = new char[andBmpSize];
ico.read(xorBmp, xorBmpSize);
if (!ico.good()) goto cleanup;
ico.read(andBmp, andBmpSize);
if (!ico.good()) goto cleanup;
image = x11_pipe->_XcursorImageCreate(infoHeader.width, infoHeader.height / 2);
// Support all the formats that GIMP supports.
switch (bitsPerPixel) {
case 1:
case 4:
case 8:
// For colors less that a byte wide, shift and mask the palette indices
// off each element of the xorBmp and append them to the image.
mask = ((1 << bitsPerPixel) - 1);
for (i = image->height - 1; i >= 0; i--) {
for (j = 0; j < image->width; j += 8 / bitsPerPixel) {
for (k = 0; k < 8 / bitsPerPixel; k++) {
shift = 8 - ((k + 1) * bitsPerPixel);
color = palette[(*curXor & (mask << shift)) >> shift];
image->pixels[(i * image->width) + j + k] = (color.red << 16) +
(color.green << 8) +
(color.blue);
}
curXor++;
}
// Set the alpha byte properly according to the andBmp.
for (j = 0; j < image->width; j += 8) {
for (k = 0; k < 8; k++) {
shift = 7 - k;
image->pixels[(i * image->width) + j + k] |=
((*curAnd & (1 << shift)) >> shift) ? 0x0 : (0xff << 24);
}
curAnd++;
}
}
break;
case 24:
// Pack each of the three bytes into a single color, BGR -> 0RGB
for (i = image->height - 1; i >= 0; i--) {
for (j = 0; j < image->width; j++) {
shift = 7 - (j & 0x7);
uint32_t alpha = (curAnd[j >> 3] & (1 << shift)) ? 0 : 0xff000000U;
image->pixels[(i * image->width) + j] = (uint8_t)curXor[0]
| ((uint8_t)curXor[1] << 8u)
| ((uint8_t)curXor[2] << 16u)
| alpha;
curXor += 3;
}
curAnd += and_stride;
}
break;
case 32:
// Pack each of the four bytes into a single color, BGRA -> ARGB
for (i = image->height - 1; i >= 0; i--) {
for (j = 0; j < image->width; j++) {
image->pixels[(i * image->width) + j] = (*(curXor + 3) << 24) +
(*(curXor + 2) << 16) +
(*(curXor + 1) << 8) +
(*curXor);
curXor += 4;
}
}
break;
default:
goto cleanup;
}
}
// If this is an actual CUR not an ICO set up the hotspot properly.
if (header.type == 2) {
image->xhot = entries[entry].xhot;
image->yhot = entries[entry].yhot;
} else {
image->xhot = 0;
image->yhot = 0;
}
ret = x11_pipe->_XcursorImageLoadCursor(_display, image);
cleanup:
x11_pipe->_XcursorImageDestroy(image);
delete[] entries;
delete[] palette;
delete[] xorBmp;
delete[] andBmp;
return ret;
}