mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-17 12:12:10 -04:00
2513 lines
79 KiB
C++
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;
|
|
}
|