godot/platform/x11/os_x11.cpp

2420 lines
68 KiB
C++
Raw Normal View History

2014-02-10 01:10:30 +00:00
/*************************************************************************/
/* os_x11.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
2014-02-10 01:10:30 +00:00
/*************************************************************************/
/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
2014-02-10 01:10:30 +00:00
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "os_x11.h"
#include "drivers/gles2/rasterizer_gles2.h"
#include "errno.h"
2014-02-10 01:10:30 +00:00
#include "key_mapping_x11.h"
#include "print_string.h"
#include "servers/physics/physics_server_sw.h"
#include "servers/visual/visual_server_raster.h"
2014-02-10 01:10:30 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2014-02-10 01:10:30 +00:00
#include "X11/Xutil.h"
#include "X11/Xatom.h"
#include "X11/extensions/Xinerama.h"
#include "scene/resources/texture.h"
// ICCCM
#define WM_NormalState 1L // window normal state
#define WM_IconicState 3L // window minimized
// EWMH
#define _NET_WM_STATE_REMOVE 0L // remove/unset property
#define _NET_WM_STATE_ADD 1L // add/set property
#define _NET_WM_STATE_TOGGLE 2L // toggle property
2014-02-10 01:10:30 +00:00
#include "main/main.h"
#include <dlfcn.h>
2014-02-10 01:10:30 +00:00
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
2014-02-10 01:10:30 +00:00
#include <unistd.h>
2014-02-23 20:20:27 +00:00
2014-02-10 01:10:30 +00:00
//stupid linux.h
#ifdef KEY_TAB
#undef KEY_TAB
#endif
#include <X11/Xatom.h>
#undef CursorShape
2018-01-23 00:15:43 +00:00
#include <X11/XKBlib.h>
2014-02-10 01:10:30 +00:00
int OS_X11::get_video_driver_count() const {
return 1;
2014-02-10 01:10:30 +00:00
}
const char *OS_X11::get_video_driver_name(int p_driver) const {
return "GLES2";
2014-02-10 01:10:30 +00:00
}
2015-10-25 17:36:27 +00:00
OS::VideoMode OS_X11::get_default_video_mode() const {
return OS::VideoMode(1024, 600, false);
2014-02-10 01:10:30 +00:00
}
int OS_X11::get_audio_driver_count() const {
return AudioDriverManagerSW::get_driver_count();
}
const char *OS_X11::get_audio_driver_name(int p_driver) const {
AudioDriverSW *driver = AudioDriverManagerSW::get_driver(p_driver);
ERR_FAIL_COND_V(!driver, "");
return AudioDriverManagerSW::get_driver(p_driver)->get_name();
}
void OS_X11::initialize_core() {
crash_handler.initialize();
OS_Unix::initialize_core();
}
void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
2014-02-10 01:10:30 +00:00
last_button_state = 0;
2014-02-10 01:10:30 +00:00
xmbstring = NULL;
event_id = 0;
x11_window = 0;
last_click_ms = 0;
args = OS::get_singleton()->get_cmdline_args();
current_videomode = p_desired;
main_loop = NULL;
last_timestamp = 0;
last_mouse_pos_valid = false;
last_keyrelease_time = 0;
xdnd_version = 0;
2014-02-10 01:10:30 +00:00
if (get_render_thread_mode() == RENDER_SEPARATE_THREAD) {
2014-02-10 01:10:30 +00:00
XInitThreads();
}
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
/** XLIB INITIALIZATION **/
x11_display = XOpenDisplay(NULL);
2016-03-08 23:00:52 +00:00
char *modifiers = XSetLocaleModifiers("@im=none");
if (modifiers == NULL) {
WARN_PRINT("Error setting locale modifiers");
}
2016-03-08 23:00:52 +00:00
const char *err;
xrr_get_monitors = NULL;
2016-06-12 13:29:02 +00:00
xrr_free_monitors = NULL;
int xrandr_major = 0;
int xrandr_minor = 0;
int event_base, error_base;
xrandr_ext_ok = XRRQueryExtension(x11_display, &event_base, &error_base);
xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);
if (!xrandr_handle) {
err = dlerror();
fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err);
} else {
2016-06-12 13:29:02 +00:00
XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);
if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {
xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors");
2016-06-12 13:29:02 +00:00
if (!xrr_get_monitors) {
err = dlerror();
fprintf(stderr, "could not find symbol XRRGetMonitors\nError: %s\n", err);
} else {
xrr_free_monitors = (xrr_free_monitors_t)dlsym(xrandr_handle, "XRRFreeMonitors");
2016-06-12 13:29:02 +00:00
if (!xrr_free_monitors) {
err = dlerror();
fprintf(stderr, "could not find XRRFreeMonitors\nError: %s\n", err);
xrr_get_monitors = NULL;
}
}
}
}
2017-11-29 20:01:20 +00:00
#ifdef TOUCH_ENABLED
if (!XQueryExtension(x11_display, "XInputExtension", &touch.opcode, &event_base, &error_base)) {
fprintf(stderr, "XInput extension not available");
} else {
// 2.2 is the first release with multitouch
int xi_major = 2;
int xi_minor = 2;
if (XIQueryVersion(x11_display, &xi_major, &xi_minor) != Success) {
fprintf(stderr, "XInput 2.2 not available (server supports %d.%d)\n", xi_major, xi_minor);
} else {
int dev_count;
XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);
for (int i = 0; i < dev_count; i++) {
XIDeviceInfo *dev = &info[i];
if (!dev->enabled)
continue;
if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave))
continue;
2017-11-29 20:01:20 +00:00
bool direct_touch = false;
for (int j = 0; j < dev->num_classes; j++) {
if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {
direct_touch = true;
break;
}
}
if (direct_touch) {
touch.devices.push_back(dev->deviceid);
fprintf(stderr, "Using touch device: %s\n", dev->name);
}
}
XIFreeDeviceInfo(info);
if (is_stdout_verbose() && !touch.devices.size()) {
fprintf(stderr, "No touch devices found\n");
2017-11-29 20:01:20 +00:00
}
}
}
#endif
xim = XOpenIM(x11_display, NULL, NULL, NULL);
2014-02-10 01:10:30 +00:00
if (xim == NULL) {
WARN_PRINT("XOpenIM failed");
xim_style = 0L;
2014-02-10 01:10:30 +00:00
} else {
::XIMStyles *xim_styles = NULL;
xim_style = 0L;
char *imvalret = NULL;
2014-02-10 01:10:30 +00:00
imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL);
if (imvalret != NULL || xim_styles == NULL) {
fprintf(stderr, "Input method doesn't support any styles\n");
2014-02-10 01:10:30 +00:00
}
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
if (xim_styles) {
2015-01-16 05:44:41 +00:00
xim_style = 0L;
for (int i = 0; i < xim_styles->count_styles; i++) {
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
if (xim_styles->supported_styles[i] ==
(XIMPreeditNothing | XIMStatusNothing)) {
2016-03-08 23:00:52 +00:00
xim_style = xim_styles->supported_styles[i];
break;
}
2014-02-10 01:10:30 +00:00
}
2016-03-08 23:00:52 +00:00
XFree(xim_styles);
2014-02-10 01:10:30 +00:00
}
XFree(imvalret);
2014-02-10 01:10:30 +00:00
}
/*
2014-02-10 01:10:30 +00:00
char* windowid = getenv("GODOT_WINDOWID");
if (windowid) {
//freopen("/home/punto/stdout", "w", stdout);
//reopen("/home/punto/stderr", "w", stderr);
x11_window = atol(windowid);
XWindowAttributes xwa;
XGetWindowAttributes(x11_display,x11_window,&xwa);
current_videomode.width = xwa.width;
current_videomode.height = xwa.height;
};
*/
// maybe contextgl wants to be in charge of creating the window
//print_line("def videomode "+itos(current_videomode.width)+","+itos(current_videomode.height));
2014-02-10 01:10:30 +00:00
#if defined(OPENGL_ENABLED) || defined(LEGACYGL_ENABLED)
context_gl = memnew(ContextGL_X11(x11_display, x11_window, current_videomode, false));
2014-02-10 01:10:30 +00:00
context_gl->initialize();
rasterizer = memnew(RasterizerGLES2);
2014-02-10 01:10:30 +00:00
#endif
visual_server = memnew(VisualServerRaster(rasterizer));
2014-02-10 01:10:30 +00:00
if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
2014-02-10 01:10:30 +00:00
visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD));
2014-02-10 01:10:30 +00:00
}
// borderless fullscreen window mode
if (current_videomode.fullscreen) {
current_videomode.fullscreen = false;
set_window_fullscreen(true);
}
if (current_videomode.always_on_top) {
current_videomode.always_on_top = false;
set_window_always_on_top(true);
}
// disable resizable window
if (!current_videomode.resizable) {
XSizeHints *xsh;
xsh = XAllocSizeHints();
xsh->flags = PMinSize | PMaxSize;
XWindowAttributes xwa;
if (current_videomode.fullscreen) {
XGetWindowAttributes(x11_display, DefaultRootWindow(x11_display), &xwa);
2014-09-16 06:49:31 +00:00
} else {
XGetWindowAttributes(x11_display, x11_window, &xwa);
}
2016-03-08 23:00:52 +00:00
xsh->min_width = xwa.width;
xsh->max_width = xwa.width;
xsh->min_height = xwa.height;
xsh->max_height = xwa.height;
XSetWMNormalHints(x11_display, x11_window, xsh);
XFree(xsh);
}
2014-02-10 01:10:30 +00:00
AudioDriverManagerSW::get_driver(p_audio_driver)->set_singleton();
audio_driver_index = p_audio_driver;
if (AudioDriverManagerSW::get_driver(p_audio_driver)->init() != OK) {
2014-02-10 01:10:30 +00:00
bool success = false;
audio_driver_index = -1;
for (int i = 0; i < AudioDriverManagerSW::get_driver_count(); i++) {
if (i == p_audio_driver)
continue;
2015-07-23 16:35:47 +00:00
AudioDriverManagerSW::get_driver(i)->set_singleton();
if (AudioDriverManagerSW::get_driver(i)->init() == OK) {
success = true;
print_line("Audio Driver Failed: " + String(AudioDriverManagerSW::get_driver(p_audio_driver)->get_name()));
print_line("Using alternate audio driver: " + String(AudioDriverManagerSW::get_driver(i)->get_name()));
audio_driver_index = i;
break;
}
}
if (!success) {
ERR_PRINT("Initializing audio failed.");
}
2014-02-10 01:10:30 +00:00
}
sample_manager = memnew(SampleManagerMallocSW);
audio_server = memnew(AudioServerSW(sample_manager));
2014-02-10 01:10:30 +00:00
audio_server->init();
spatial_sound_server = memnew(SpatialSoundServerSW);
2014-02-10 01:10:30 +00:00
spatial_sound_server->init();
spatial_sound_2d_server = memnew(SpatialSound2DServerSW);
2014-02-10 01:10:30 +00:00
spatial_sound_2d_server->init();
ERR_FAIL_COND(!visual_server);
ERR_FAIL_COND(x11_window == 0);
2014-02-10 01:10:30 +00:00
XSetWindowAttributes new_attr;
new_attr.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |
ButtonReleaseMask | EnterWindowMask |
LeaveWindowMask | PointerMotionMask |
Button1MotionMask |
Button2MotionMask | Button3MotionMask |
Button4MotionMask | Button5MotionMask |
ButtonMotionMask | KeymapStateMask |
ExposureMask | VisibilityChangeMask |
StructureNotifyMask |
SubstructureNotifyMask | SubstructureRedirectMask |
FocusChangeMask | PropertyChangeMask |
ColormapChangeMask | OwnerGrabButtonMask;
2014-02-10 01:10:30 +00:00
XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr);
2016-03-08 23:00:52 +00:00
2017-11-29 20:01:20 +00:00
#ifdef TOUCH_ENABLED
if (touch.devices.size()) {
// Must be alive after this block
static unsigned char mask_data[XIMaskLen(XI_LASTEVENT)] = {};
touch.event_mask.deviceid = XIAllDevices;
2017-11-29 20:01:20 +00:00
touch.event_mask.mask_len = sizeof(mask_data);
touch.event_mask.mask = mask_data;
XISetMask(touch.event_mask.mask, XI_TouchBegin);
XISetMask(touch.event_mask.mask, XI_TouchUpdate);
XISetMask(touch.event_mask.mask, XI_TouchEnd);
XISetMask(touch.event_mask.mask, XI_TouchOwnership);
XISelectEvents(x11_display, x11_window, &touch.event_mask, 1);
// Disabled by now since grabbing also blocks mouse events
// (they are received as extended events instead of standard events)
/*XIClearMask(touch.event_mask.mask, XI_TouchOwnership);
2017-11-29 20:01:20 +00:00
// Grab touch devices to avoid OS gesture interference
for (int i = 0; i < touch.devices.size(); ++i) {
XIGrabDevice(x11_display, touch.devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &touch.event_mask);
}*/
2017-11-29 20:01:20 +00:00
}
#endif
XClassHint *classHint;
/* set the titlebar name */
XStoreName(x11_display, x11_window, "Godot");
/* set the name and class hints for the window manager to use */
classHint = XAllocClassHint();
if (classHint) {
classHint->res_name = (char *)"Godot_Engine";
2015-05-05 22:56:59 +00:00
classHint->res_class = (char *)"Godot";
}
XSetClassHint(x11_display, x11_window, classHint);
XFree(classHint);
2016-03-08 23:00:52 +00:00
wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);
2014-02-10 01:10:30 +00:00
XSetWMProtocols(x11_display, x11_window, &wm_delete, 1);
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
if (xim && xim_style) {
2016-03-08 23:00:52 +00:00
xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, x11_window, XNFocusWindow, x11_window, (char *)NULL);
2014-02-10 01:10:30 +00:00
} else {
2016-03-08 23:00:52 +00:00
xic = NULL;
2014-02-10 01:10:30 +00:00
WARN_PRINT("XCreateIC couldn't create xic");
2016-03-08 23:00:52 +00:00
}
2014-02-10 01:10:30 +00:00
cursor_size = XcursorGetDefaultSize(x11_display);
cursor_theme = XcursorGetTheme(x11_display);
if (!cursor_theme) {
if (is_stdout_verbose()) {
print_line("XcursorGetTheme could not get cursor theme");
}
cursor_theme = "default";
2014-02-10 01:10:30 +00:00
}
for (int i = 0; i < CURSOR_MAX; i++) {
2014-02-10 01:10:30 +00:00
cursors[i] = None;
img[i] = NULL;
2014-02-10 01:10:30 +00:00
}
current_cursor = CURSOR_ARROW;
2014-02-10 01:10:30 +00:00
if (cursor_theme) {
//print_line("cursor theme: "+String(cursor_theme));
for (int i = 0; i < CURSOR_MAX; i++) {
2014-02-10 01:10:30 +00:00
static const char *cursor_file[] = {
2014-02-10 01:10:30 +00:00
"left_ptr",
"xterm",
"hand2",
"cross",
"watch",
"left_ptr_watch",
"fleur",
"hand1",
"X_cursor",
"sb_v_double_arrow",
"sb_h_double_arrow",
"size_bdiag",
"size_fdiag",
"hand1",
"sb_v_double_arrow",
"sb_h_double_arrow",
"question_arrow"
};
img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size);
2015-10-25 14:15:56 +00:00
if (img[i]) {
cursors[i] = XcursorImageLoadCursor(x11_display, img[i]);
2014-02-10 01:10:30 +00:00
//print_line("found cursor: "+String(cursor_file[i])+" id "+itos(cursors[i]));
} else {
if (OS::is_stdout_verbose())
print_line("failed cursor: " + String(cursor_file[i]));
2014-02-10 01:10:30 +00:00
}
}
}
{
Pixmap cursormask;
XGCValues xgc;
GC gc;
XColor col;
Cursor cursor;
2014-02-10 01:10:30 +00:00
cursormask = XCreatePixmap(x11_display, RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);
2015-10-25 14:15:56 +00:00
xgc.function = GXclear;
gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);
2014-02-10 01:10:30 +00:00
XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);
col.pixel = 0;
col.red = 0;
col.flags = 4;
cursor = XCreatePixmapCursor(x11_display,
cursormask, cursormask,
&col, &col, 0, 0);
XFreePixmap(x11_display, cursormask);
XFreeGC(x11_display, gc);
2014-02-10 01:10:30 +00:00
if (cursor == None) {
ERR_PRINT("FAILED CREATING CURSOR");
}
2014-02-10 01:10:30 +00:00
null_cursor = cursor;
2014-02-10 01:10:30 +00:00
}
set_cursor_shape(CURSOR_BUSY);
//Set Xdnd (drag & drop) support
Atom XdndAware = XInternAtom(x11_display, "XdndAware", False);
Atom version = 5;
XChangeProperty(x11_display, x11_window, XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&version, 1);
xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);
xdnd_position = XInternAtom(x11_display, "XdndPosition", False);
xdnd_status = XInternAtom(x11_display, "XdndStatus", False);
xdnd_action_copy = XInternAtom(x11_display, "XdndActionCopy", False);
xdnd_drop = XInternAtom(x11_display, "XdndDrop", False);
xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);
xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);
requested = None;
2014-02-10 01:10:30 +00:00
visual_server->init();
//
physics_server = memnew(PhysicsServerSW);
2014-02-10 01:10:30 +00:00
physics_server->init();
//physics_2d_server = memnew( Physics2DServerSW );
physics_2d_server = Physics2DServerWrapMT::init_server<Physics2DServerSW>();
2014-02-10 01:10:30 +00:00
physics_2d_server->init();
input = memnew(InputDefault);
#ifdef JOYDEV_ENABLED
joystick = memnew(joystick_linux(input));
2015-12-18 05:12:53 +00:00
#endif
2014-02-10 01:10:30 +00:00
_ensure_data_dir();
}
2015-10-25 14:15:56 +00:00
2014-02-10 01:10:30 +00:00
void OS_X11::finalize() {
if (main_loop)
2014-02-10 01:10:30 +00:00
memdelete(main_loop);
main_loop = NULL;
2014-02-10 01:10:30 +00:00
spatial_sound_server->finish();
memdelete(spatial_sound_server);
spatial_sound_2d_server->finish();
memdelete(spatial_sound_2d_server);
//if (debugger_connection_console) {
// memdelete(debugger_connection_console);
//}
2014-02-10 01:10:30 +00:00
2016-01-05 22:35:54 +00:00
#ifdef JOYDEV_ENABLED
memdelete(joystick);
2017-11-29 20:01:20 +00:00
#endif
#ifdef TOUCH_ENABLED
touch.devices.clear();
touch.state.clear();
2016-01-05 22:35:54 +00:00
#endif
memdelete(input);
memdelete(sample_manager);
2014-02-10 01:10:30 +00:00
audio_server->finish();
memdelete(audio_server);
visual_server->finish();
memdelete(visual_server);
memdelete(rasterizer);
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
physics_server->finish();
memdelete(physics_server);
physics_2d_server->finish();
memdelete(physics_2d_server);
if (xrandr_handle)
dlclose(xrandr_handle);
XUnmapWindow(x11_display, x11_window);
XDestroyWindow(x11_display, x11_window);
2015-10-25 14:15:56 +00:00
2014-02-10 01:10:30 +00:00
#if defined(OPENGL_ENABLED) || defined(LEGACYGL_ENABLED)
memdelete(context_gl);
#endif
for (int i = 0; i < CURSOR_MAX; i++) {
if (cursors[i] != None)
XFreeCursor(x11_display, cursors[i]);
if (img[i] != NULL)
XcursorImageDestroy(img[i]);
2016-03-08 23:00:52 +00:00
};
2015-10-25 14:15:56 +00:00
XDestroyIC(xic);
XCloseIM(xim);
2014-02-10 01:10:30 +00:00
XCloseDisplay(x11_display);
if (xmbstring)
memfree(xmbstring);
2015-10-25 14:15:56 +00:00
2014-02-10 01:10:30 +00:00
args.clear();
}
void OS_X11::set_mouse_mode(MouseMode p_mode) {
if (p_mode == mouse_mode)
2014-02-10 01:10:30 +00:00
return;
if (mouse_mode == MOUSE_MODE_CAPTURED)
2014-02-10 01:10:30 +00:00
XUngrabPointer(x11_display, CurrentTime);
if (mouse_mode != MOUSE_MODE_VISIBLE && p_mode == MOUSE_MODE_VISIBLE)
XDefineCursor(x11_display, x11_window, cursors[current_cursor]);
if (p_mode != MOUSE_MODE_VISIBLE && mouse_mode == MOUSE_MODE_VISIBLE) {
XDefineCursor(x11_display, x11_window, null_cursor);
2014-02-10 01:10:30 +00:00
}
mouse_mode = p_mode;
2014-02-10 01:10:30 +00:00
if (mouse_mode == MOUSE_MODE_CAPTURED) {
while (true) {
//flush pending motion events
if (XPending(x11_display) > 0) {
XEvent event;
XPeekEvent(x11_display, &event);
if (event.type == MotionNotify) {
XNextEvent(x11_display, &event);
} else {
break;
}
} else {
break;
}
}
if (XGrabPointer(
x11_display, x11_window, True,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime) != GrabSuccess) {
2014-02-10 01:10:30 +00:00
ERR_PRINT("NO GRAB");
}
center.x = current_videomode.width / 2;
center.y = current_videomode.height / 2;
2014-02-10 01:10:30 +00:00
XWarpPointer(x11_display, None, x11_window,
0, 0, 0, 0, (int)center.x, (int)center.y);
2014-02-10 01:10:30 +00:00
2015-02-12 14:58:00 +00:00
input->set_mouse_pos(center);
} else {
do_mouse_warp = false;
2015-02-12 14:58:00 +00:00
}
XFlush(x11_display);
2014-02-10 01:10:30 +00:00
}
void OS_X11::warp_mouse_pos(const Point2 &p_to) {
if (mouse_mode == MOUSE_MODE_CAPTURED) {
last_mouse_pos = p_to;
} else {
/*XWindowAttributes xwa;
XGetWindowAttributes(x11_display, x11_window, &xwa);
printf("%d %d\n", xwa.x, xwa.y); needed? */
XWarpPointer(x11_display, None, x11_window,
0, 0, 0, 0, (int)p_to.x, (int)p_to.y);
}
}
2014-02-10 01:10:30 +00:00
OS::MouseMode OS_X11::get_mouse_mode() const {
return mouse_mode;
}
int OS_X11::get_mouse_button_state() const {
return last_button_state;
}
Point2 OS_X11::get_mouse_pos() const {
return last_mouse_pos;
}
void OS_X11::set_window_title(const String &p_title) {
XStoreName(x11_display, x11_window, p_title.utf8().get_data());
2016-02-03 23:39:53 +00:00
Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);
Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);
XChangeProperty(x11_display, x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length());
2014-02-10 01:10:30 +00:00
}
void OS_X11::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
2014-02-10 01:10:30 +00:00
}
2015-10-25 17:36:27 +00:00
OS::VideoMode OS_X11::get_video_mode(int p_screen) const {
2014-02-10 01:10:30 +00:00
return current_videomode;
}
void OS_X11::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
2014-02-10 01:10:30 +00:00
}
void OS_X11::set_wm_fullscreen(bool p_enabled) {
if (p_enabled && !is_window_resizable()) {
// Set the window as resizable to prevent window managers to ignore the fullscreen state flag.
XSizeHints *xsh;
xsh = XAllocSizeHints();
xsh->flags = 0L;
XSetWMNormalHints(x11_display, x11_window, xsh);
XFree(xsh);
}
2018-01-05 19:10:52 +00:00
// needed for lxde/openbox, possibly others
Hints hints;
Atom property;
hints.flags = 2;
hints.decorations = 0;
property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
if (p_enabled) {
XMapRaised(x11_display, x11_window);
XWindowAttributes xwa;
XGetWindowAttributes(x11_display, DefaultRootWindow(x11_display), &xwa);
XMoveResizeWindow(x11_display, x11_window, 0, 0, xwa.width, xwa.height);
} else {
XResizeWindow(x11_display, x11_window, current_videomode.width, current_videomode.height);
}
2018-01-05 19:10:52 +00:00
// code for netwm-compliants
XEvent xev;
Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
2018-01-05 19:10:52 +00:00
Atom fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);
memset(&xev, 0, sizeof(xev));
xev.type = ClientMessage;
xev.xclient.window = x11_window;
xev.xclient.message_type = wm_state;
xev.xclient.format = 32;
xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
2018-01-05 19:10:52 +00:00
xev.xclient.data.l[1] = fullscreen;
xev.xclient.data.l[2] = 0;
2018-01-05 19:10:52 +00:00
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureNotifyMask, &xev);
// set bypass compositor hint
Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);
unsigned long compositing_disable_on = p_enabled ? 1 : 0;
XChangeProperty(x11_display, x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);
XFlush(x11_display);
if (!p_enabled && !is_window_resizable()) {
// Reset the non-resizable flags if we un-set these before.
Size2 size = get_window_size();
XSizeHints *xsh;
xsh = XAllocSizeHints();
xsh->flags = PMinSize | PMaxSize;
xsh->min_width = size.x;
xsh->max_width = size.x;
xsh->min_height = size.y;
xsh->max_height = size.y;
XSetWMNormalHints(x11_display, x11_window, xsh);
XFree(xsh);
}
}
void OS_X11::set_wm_above(bool p_enabled) {
Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
Atom wm_above = XInternAtom(x11_display, "_NET_WM_STATE_ABOVE", False);
XClientMessageEvent xev;
memset(&xev, 0, sizeof(xev));
xev.type = ClientMessage;
xev.window = x11_window;
xev.message_type = wm_state;
xev.format = 32;
xev.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
xev.data.l[1] = wm_above;
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);
}
2015-01-11 10:52:42 +00:00
int OS_X11::get_screen_count() const {
// Using Xinerama Extension
int event_base, error_base;
const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
if (!ext_okay) return 0;
2016-03-08 23:00:52 +00:00
int count;
XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
XFree(xsi);
return count;
2015-01-11 10:52:42 +00:00
}
int OS_X11::get_current_screen() const {
int x, y;
Window child;
XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
2015-01-14 04:02:59 +00:00
int count = get_screen_count();
for (int i = 0; i < count; i++) {
2015-01-14 04:02:59 +00:00
Point2i pos = get_screen_position(i);
Size2i size = get_screen_size(i);
if ((x >= pos.x && x < pos.x + size.width) && (y >= pos.y && y < pos.y + size.height))
2016-03-08 23:00:52 +00:00
return i;
2015-01-14 04:02:59 +00:00
}
return 0;
}
void OS_X11::set_current_screen(int p_screen) {
2015-01-14 04:02:59 +00:00
int count = get_screen_count();
if (p_screen >= count) return;
2016-03-08 23:00:52 +00:00
if (current_videomode.fullscreen) {
2015-01-14 04:02:59 +00:00
Point2i position = get_screen_position(p_screen);
Size2i size = get_screen_size(p_screen);
XMoveResizeWindow(x11_display, x11_window, position.x, position.y, size.x, size.y);
} else {
if (p_screen != get_current_screen()) {
2015-01-14 04:02:59 +00:00
Point2i position = get_screen_position(p_screen);
XMoveWindow(x11_display, x11_window, position.x, position.y);
}
}
}
2015-01-13 09:25:50 +00:00
Point2 OS_X11::get_screen_position(int p_screen) const {
// Using Xinerama Extension
int event_base, error_base;
const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
if (!ext_okay) {
return Point2i(0, 0);
}
2016-03-08 23:00:52 +00:00
int count;
XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
if (p_screen >= count) {
return Point2i(0, 0);
}
2016-03-08 23:00:52 +00:00
Point2i position = Point2i(xsi[p_screen].x_org, xsi[p_screen].y_org);
XFree(xsi);
return position;
2015-01-13 09:25:50 +00:00
}
2015-01-11 11:35:53 +00:00
Size2 OS_X11::get_screen_size(int p_screen) const {
// Using Xinerama Extension
int event_base, error_base;
const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
if (!ext_okay) return Size2i(0, 0);
2016-03-08 23:00:52 +00:00
int count;
XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
if (p_screen >= count) return Size2i(0, 0);
2016-03-08 23:00:52 +00:00
Size2i size = Point2i(xsi[p_screen].width, xsi[p_screen].height);
XFree(xsi);
return size;
2015-01-11 11:35:53 +00:00
}
int OS_X11::get_screen_dpi(int p_screen) const {
//invalid screen?
ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0);
//Get physical monitor Dimensions through XRandR and calculate dpi
Size2 sc = get_screen_size(p_screen);
2016-06-12 13:29:02 +00:00
if (xrandr_ext_ok) {
int count = 0;
if (xrr_get_monitors) {
xrr_monitor_info *monitors = xrr_get_monitors(x11_display, x11_window, true, &count);
if (p_screen < count) {
double xdpi = sc.width / (double)monitors[p_screen].mwidth * 25.4;
double ydpi = sc.height / (double)monitors[p_screen].mheight * 25.4;
2016-06-12 13:29:02 +00:00
xrr_free_monitors(monitors);
return (xdpi + ydpi) / 2;
}
2016-06-12 13:29:02 +00:00
xrr_free_monitors(monitors);
} else if (p_screen == 0) {
XRRScreenSize *sizes = XRRSizes(x11_display, 0, &count);
if (sizes) {
double xdpi = sc.width / (double)sizes[0].mwidth * 25.4;
double ydpi = sc.height / (double)sizes[0].mheight * 25.4;
return (xdpi + ydpi) / 2;
}
}
}
int width_mm = DisplayWidthMM(x11_display, p_screen);
int height_mm = DisplayHeightMM(x11_display, p_screen);
double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0);
double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0);
if (xdpi || xdpi)
return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);
//could not get dpi
return 96;
}
Point2 OS_X11::get_window_position() const {
int x, y;
Window child;
XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
2015-03-23 00:31:38 +00:00
int screen = get_current_screen();
Point2i screen_position = get_screen_position(screen);
return Point2i(x - screen_position.x, y - screen_position.y);
}
void OS_X11::set_window_position(const Point2 &p_position) {
XMoveWindow(x11_display, x11_window, p_position.x, p_position.y);
}
Size2 OS_X11::get_window_size() const {
// Use current_videomode width and height instead of XGetWindowAttributes
// since right after a XResizeWindow the attributes may not be updated yet
return Size2i(current_videomode.width, current_videomode.height);
}
Size2 OS_X11::get_real_window_size() const {
XWindowAttributes xwa;
XSync(x11_display, False);
XGetWindowAttributes(x11_display, x11_window, &xwa);
int w = xwa.width;
int h = xwa.height;
Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
Atom type;
int format;
unsigned long len;
unsigned long remaining;
unsigned char *data = NULL;
if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
long *extents = (long *)data;
w += extents[0] + extents[1]; // left, right
h += extents[2] + extents[3]; // top, bottom
}
return Size2(w, h);
}
void OS_X11::set_window_size(const Size2 p_size) {
// If window resizable is disabled we need to update the attributes first
if (is_window_resizable() == false) {
XSizeHints *xsh;
xsh = XAllocSizeHints();
xsh->flags = PMinSize | PMaxSize;
xsh->min_width = p_size.x;
xsh->max_width = p_size.x;
xsh->min_height = p_size.y;
xsh->max_height = p_size.y;
XSetWMNormalHints(x11_display, x11_window, xsh);
XFree(xsh);
}
// Resize the window
XResizeWindow(x11_display, x11_window, p_size.x, p_size.y);
// Update our videomode width and height
current_videomode.width = p_size.x;
current_videomode.height = p_size.y;
}
void OS_X11::set_window_fullscreen(bool p_enabled) {
if (p_enabled == current_videomode.fullscreen)
return;
if (p_enabled && current_videomode.always_on_top) {
// Fullscreen + Always-on-top requires a maximized window on some window managers (Metacity)
set_window_maximized(true);
}
set_wm_fullscreen(p_enabled);
if (!p_enabled && !current_videomode.always_on_top) {
// Restore
set_window_maximized(false);
}
current_videomode.fullscreen = p_enabled;
}
bool OS_X11::is_window_fullscreen() const {
return current_videomode.fullscreen;
}
void OS_X11::set_window_resizable(bool p_enabled) {
XSizeHints *xsh;
Size2 size = get_window_size();
xsh = XAllocSizeHints();
xsh->flags = p_enabled ? 0L : PMinSize | PMaxSize;
if (!p_enabled) {
xsh->min_width = size.x;
xsh->max_width = size.x;
xsh->min_height = size.y;
xsh->max_height = size.y;
}
XSetWMNormalHints(x11_display, x11_window, xsh);
XFree(xsh);
current_videomode.resizable = p_enabled;
}
bool OS_X11::is_window_resizable() const {
return current_videomode.resizable;
}
void OS_X11::set_window_minimized(bool p_enabled) {
2016-07-21 19:11:34 +00:00
// Using ICCCM -- Inter-Client Communication Conventions Manual
XEvent xev;
Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);
2016-07-21 19:11:34 +00:00
memset(&xev, 0, sizeof(xev));
xev.type = ClientMessage;
xev.xclient.window = x11_window;
xev.xclient.message_type = wm_change;
xev.xclient.format = 32;
xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState;
2016-07-21 19:11:34 +00:00
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
2016-03-08 23:00:52 +00:00
Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);
memset(&xev, 0, sizeof(xev));
xev.type = ClientMessage;
xev.xclient.window = x11_window;
xev.xclient.message_type = wm_state;
xev.xclient.format = 32;
xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
xev.xclient.data.l[1] = wm_hidden;
2016-03-08 23:00:52 +00:00
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}
bool OS_X11::is_window_minimized() const {
// Using ICCCM -- Inter-Client Communication Conventions Manual
Atom property = XInternAtom(x11_display, "WM_STATE", True);
Atom type;
int format;
unsigned long len;
unsigned long remaining;
unsigned char *data = NULL;
int result = XGetWindowProperty(
x11_display,
x11_window,
property,
0,
32,
False,
AnyPropertyType,
&type,
&format,
&len,
&remaining,
&data);
if (result == Success) {
long *state = (long *)data;
if (state[0] == WM_IconicState)
return true;
}
return false;
}
void OS_X11::set_window_maximized(bool p_enabled) {
2016-03-08 23:00:52 +00:00
// Using EWMH -- Extended Window Manager Hints
XEvent xev;
Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
memset(&xev, 0, sizeof(xev));
xev.type = ClientMessage;
xev.xclient.window = x11_window;
xev.xclient.message_type = wm_state;
xev.xclient.format = 32;
xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
xev.xclient.data.l[1] = wm_max_horz;
xev.xclient.data.l[2] = wm_max_vert;
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
maximized = p_enabled;
}
bool OS_X11::is_window_maximized() const {
// Using EWMH -- Extended Window Manager Hints
Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False);
2016-07-21 19:11:34 +00:00
Atom type;
int format;
unsigned long len;
unsigned long remaining;
unsigned char *data = NULL;
2016-07-21 19:11:34 +00:00
int result = XGetWindowProperty(
x11_display,
x11_window,
property,
0,
1024,
False,
XA_ATOM,
&type,
&format,
&len,
&remaining,
&data);
if (result == Success) {
Atom *atoms = (Atom *)data;
Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
bool found_wm_max_horz = false;
bool found_wm_max_vert = false;
for (unsigned int i = 0; i < len; i++) {
if (atoms[i] == wm_max_horz)
found_wm_max_horz = true;
if (atoms[i] == wm_max_vert)
found_wm_max_vert = true;
if (found_wm_max_horz && found_wm_max_vert)
return true;
}
XFree(atoms);
}
return false;
}
void OS_X11::set_window_always_on_top(bool p_enabled) {
if (current_videomode.always_on_top == p_enabled)
return;
if (p_enabled && current_videomode.fullscreen) {
// Fullscreen + Always-on-top requires a maximized window on some window managers (Metacity)
set_window_maximized(true);
}
set_wm_above(p_enabled);
if (!p_enabled && !current_videomode.fullscreen) {
// Restore
set_window_maximized(false);
}
current_videomode.always_on_top = p_enabled;
}
bool OS_X11::is_window_always_on_top() const {
return current_videomode.always_on_top;
}
void OS_X11::request_attention() {
// Using EWMH -- Extended Window Manager Hints
//
// Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE
// Will be unset by the window manager after user react on the request for attention
//
XEvent xev;
Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
Atom wm_attention = XInternAtom(x11_display, "_NET_WM_STATE_DEMANDS_ATTENTION", False);
memset(&xev, 0, sizeof(xev));
xev.type = ClientMessage;
xev.xclient.window = x11_window;
xev.xclient.message_type = wm_state;
xev.xclient.format = 32;
xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
xev.xclient.data.l[1] = wm_attention;
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}
2014-02-10 01:10:30 +00:00
InputModifierState OS_X11::get_key_modifier_state(unsigned int p_x11_state) {
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
InputModifierState state;
2016-03-08 23:00:52 +00:00
state.shift = (p_x11_state & ShiftMask);
state.control = (p_x11_state & ControlMask);
state.alt = (p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/); //altgr should not count as alt
state.meta = (p_x11_state & Mod4Mask);
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
return state;
}
unsigned int OS_X11::get_mouse_button_state(unsigned int p_x11_state) {
unsigned int state = 0;
2016-03-08 23:00:52 +00:00
if (p_x11_state & Button1Mask) {
2016-03-08 23:00:52 +00:00
state |= 1 << 0;
2014-02-10 01:10:30 +00:00
}
if (p_x11_state & Button3Mask) {
2016-03-08 23:00:52 +00:00
state |= 1 << 1;
2014-02-10 01:10:30 +00:00
}
if (p_x11_state & Button2Mask) {
2016-03-08 23:00:52 +00:00
state |= 1 << 2;
2014-02-10 01:10:30 +00:00
}
2016-03-08 23:00:52 +00:00
if (p_x11_state & Button4Mask) {
2016-03-08 23:00:52 +00:00
state |= 1 << 3;
2014-02-10 01:10:30 +00:00
}
if (p_x11_state & Button5Mask) {
2016-03-08 23:00:52 +00:00
state |= 1 << 4;
2014-02-10 01:10:30 +00:00
}
last_button_state = state;
2014-02-10 01:10:30 +00:00
return state;
}
2016-03-08 23:00:52 +00:00
void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// X11 functions don't know what const is
XKeyEvent *xkeyevent = p_event;
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// This code was pretty difficult to write.
// The docs stink and every toolkit seems to
2016-03-08 23:00:52 +00:00
// do it in a different way.
2014-02-10 01:10:30 +00:00
/* Phase 1, obtain a proper keysym */
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// This was also very difficult to figure out.
// You'd expect you could just use Keysym provided by
2016-03-08 23:00:52 +00:00
// XKeycodeToKeysym to obtain internationalized
// input.. WRONG!!
2014-02-10 01:10:30 +00:00
// you must use XLookupString (???) which not only wastes
// cycles generating an unnecesary string, but also
// still works in half the cases. (won't handle deadkeys)
// For more complex input methods (deadkeys and more advanced)
// you have to use XmbLookupString (??).
// So.. then you have to chosse which of both results
// you want to keep.
// This is a real bizarreness and cpu waster.
2016-03-08 23:00:52 +00:00
KeySym keysym_keycode = 0; // keysym used to find a keycode
KeySym keysym_unicode = 0; // keysym used to find unicode
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// XLookupString returns keysyms usable as nice scancodes/
char str[256 + 1];
2015-01-16 05:44:41 +00:00
XLookupString(xkeyevent, str, 256, &keysym_keycode, NULL);
2016-03-08 23:00:52 +00:00
// Meanwhile, XLookupString returns keysyms useful for unicode.
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
if (!xmbstring) {
// keep a temporary buffer for the string
xmbstring = (char *)memalloc(sizeof(char) * 8);
xmblen = 8;
2016-03-08 23:00:52 +00:00
}
2014-02-10 01:10:30 +00:00
if (xkeyevent->type == KeyPress && xic) {
Status status;
do {
2016-03-08 23:00:52 +00:00
int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);
2014-02-10 01:10:30 +00:00
xmbstring[mnbytes] = '\0';
if (status == XBufferOverflow) {
xmblen = mnbytes + 1;
xmbstring = (char *)memrealloc(xmbstring, xmblen);
2016-03-08 23:00:52 +00:00
}
2014-02-10 01:10:30 +00:00
} while (status == XBufferOverflow);
2016-03-08 23:00:52 +00:00
}
2014-02-10 01:10:30 +00:00
/* Phase 2, obtain a pigui keycode from the keysym */
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// KeyMappingX11 just translated the X11 keysym to a PIGUI
// keysym, so it works in all platforms the same.
unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
/* Phase 3, obtain an unicode character from the keysym */
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// KeyMappingX11 also translates keysym to unicode.
// It does a binary search on a table to translate
2016-03-08 23:00:52 +00:00
// most properly.
2014-02-10 01:10:30 +00:00
//print_line("keysym_unicode: "+rtos(keysym_unicode));
unsigned int unicode = keysym_unicode > 0 ? KeyMappingX11::get_unicode_from_keysym(keysym_unicode) : 0;
2014-02-10 01:10:30 +00:00
/* Phase 4, determine if event must be filtered */
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// This seems to be a side-effect of using XIM.
// XEventFilter looks like a core X11 funciton,
// but it's actually just used to see if we must
// ignore a deadkey, or events XIM determines
// must not reach the actual gui.
// Guess it was a design problem of the extension
bool keypress = xkeyevent->type == KeyPress;
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
if (xkeyevent->type == KeyPress && xic) {
if (XFilterEvent((XEvent *)xkeyevent, x11_window))
return;
2014-02-10 01:10:30 +00:00
}
2016-03-08 23:00:52 +00:00
if (keycode == 0 && unicode == 0)
2014-02-10 01:10:30 +00:00
return;
/* Phase 5, determine modifier mask */
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// No problems here, except I had no way to
// know Mod1 was ALT and Mod4 was META (applekey/winkey)
// just tried Mods until i found them.
//print_line("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
InputModifierState state = get_key_modifier_state(xkeyevent->state);
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
/* Phase 6, determine echo character */
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// Echo characters in X11 are a keyrelease and a keypress
// one after the other with the (almot) same timestamp.
// To detect them, i use XPeekEvent and check that their
// difference in time is below a treshold.
2016-03-08 23:00:52 +00:00
if (xkeyevent->type != KeyPress) {
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// make sure there are events pending,
// so this call won't block.
if (XPending(x11_display) > 0) {
2014-02-10 01:10:30 +00:00
XEvent peek_event;
XPeekEvent(x11_display, &peek_event);
2016-03-08 23:00:52 +00:00
// I'm using a treshold of 5 msecs,
2014-02-10 01:10:30 +00:00
// since sometimes there seems to be a little
// jitter. I'm still not convinced that all this approach
// is correct, but the xorg developers are
// not very helpful today.
2016-03-08 23:00:52 +00:00
::Time tresh = ABS(peek_event.xkey.time - xkeyevent->time);
if (peek_event.type == KeyPress && tresh < 5) {
KeySym rk;
XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, NULL);
if (rk == keysym_keycode) {
XEvent event;
XNextEvent(x11_display, &event); //erase next event
handle_key_event((XKeyEvent *)&event, true);
return; //ignore current, echo next
}
}
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
// use the time from peek_event so it always works
}
2016-03-08 23:00:52 +00:00
// save the time to check for echo when keypress happens
2014-02-10 01:10:30 +00:00
}
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
/* Phase 7, send event to Window */
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
InputEvent event;
event.ID = ++event_id;
2014-02-10 01:10:30 +00:00
event.type = InputEvent::KEY;
event.device = 0;
event.key.mod = state;
event.key.pressed = keypress;
2014-02-10 01:10:30 +00:00
if (keycode >= 'a' && keycode <= 'z')
keycode -= 'a' - 'A';
2014-02-10 01:10:30 +00:00
event.key.scancode = keycode;
event.key.unicode = unicode;
event.key.echo = p_echo;
2014-02-10 01:10:30 +00:00
if (event.key.scancode == KEY_BACKTAB) {
2014-02-10 01:10:30 +00:00
//make it consistent accross platforms.
event.key.scancode = KEY_TAB;
event.key.mod.shift = true;
2014-02-10 01:10:30 +00:00
}
//don't set mod state if modifier keys are released by themselves
//else event.is_action() will not work correctly here
if (!event.key.pressed) {
if (event.key.scancode == KEY_SHIFT)
event.key.mod.shift = false;
else if (event.key.scancode == KEY_CONTROL)
event.key.mod.control = false;
else if (event.key.scancode == KEY_ALT)
event.key.mod.alt = false;
else if (event.key.scancode == KEY_META)
event.key.mod.meta = false;
}
2014-02-10 01:10:30 +00:00
//printf("key: %x\n",event.key.scancode);
input->parse_input_event(event);
2014-02-10 01:10:30 +00:00
}
struct Property {
unsigned char *data;
int format, nitems;
Atom type;
};
static Property read_property(Display *p_display, Window p_window, Atom p_property) {
Atom actual_type;
int actual_format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char *ret = 0;
int read_bytes = 1024;
//Keep trying to read the property until there are no
//bytes unread.
do {
if (ret != 0)
XFree(ret);
XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes_after,
&ret);
read_bytes *= 2;
} while (bytes_after != 0);
Property p = { ret, actual_format, (int)nitems, actual_type };
return p;
}
static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count) {
static const char *target_type = "text/uri-list";
for (int i = 0; i < p_count; i++) {
Atom atom = p_list[i];
if (atom != None && String(XGetAtomName(p_display, atom)) == target_type)
return atom;
}
return None;
}
static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {
static const char *target_type = "text/uri-list";
if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type)
return p_t1;
if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type)
return p_t2;
if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type)
return p_t3;
return None;
}
2014-02-10 01:10:30 +00:00
void OS_X11::process_xevents() {
//printf("checking events %i\n", XPending(x11_display));
do_mouse_warp = false;
2014-02-10 01:10:30 +00:00
while (XPending(x11_display) > 0) {
XEvent event;
XNextEvent(x11_display, &event);
2017-11-29 20:01:20 +00:00
#ifdef TOUCH_ENABLED
if (XGetEventData(x11_display, &event.xcookie)) {
if (event.xcookie.type == GenericEvent && event.xcookie.extension == touch.opcode) {
2017-11-29 20:01:20 +00:00
InputEvent input_event;
input_event.ID = ++event_id;
input_event.device = 0;
InputEvent mouse_event;
mouse_event.ID = ++event_id;
mouse_event.device = 0;
2017-11-29 20:01:20 +00:00
XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
int index = event_data->detail;
Point2i pos = Point2i(event_data->event_x, event_data->event_y);
switch (event_data->evtype) {
case XI_TouchBegin: // Fall-through
// Disabled hand-in-hand with the grabbing
//XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch);
2017-11-29 20:01:20 +00:00
case XI_TouchEnd: {
bool is_begin = event_data->evtype == XI_TouchBegin;
bool translate = false;
if (is_begin) {
++num_touches;
if (num_touches == 1) {
touch_mouse_index = index;
translate = true;
}
} else {
--num_touches;
if (num_touches == 0) {
translate = true;
} else if (num_touches < 0) { // Defensive
num_touches = 0;
}
touch_mouse_index = -1;
}
2017-11-29 20:01:20 +00:00
input_event.type = InputEvent::SCREEN_TOUCH;
input_event.screen_touch.index = index;
input_event.screen_touch.x = pos.x;
input_event.screen_touch.y = pos.y;
input_event.screen_touch.pressed = is_begin;
if (translate) {
mouse_event.type = InputEvent::MOUSE_BUTTON;
mouse_event.mouse_button.x = pos.x;
mouse_event.mouse_button.y = pos.y;
mouse_event.mouse_button.global_x = pos.x;
mouse_event.mouse_button.global_y = pos.y;
input->set_mouse_pos(pos);
mouse_event.mouse_button.button_index = 1;
mouse_event.mouse_button.pressed = is_begin;
last_mouse_pos = pos;
}
2017-11-29 20:01:20 +00:00
if (is_begin) {
if (touch.state.has(index)) // Defensive
break;
touch.state[index] = pos;
input->parse_input_event(input_event);
input->parse_input_event(mouse_event);
2017-11-29 20:01:20 +00:00
} else {
if (!touch.state.has(index)) // Defensive
break;
touch.state.erase(index);
input->parse_input_event(input_event);
input->parse_input_event(mouse_event);
2017-11-29 20:01:20 +00:00
}
} break;
case XI_TouchUpdate: {
Map<int, Point2i>::Element *curr_pos_elem = touch.state.find(index);
if (!curr_pos_elem) // Defensive
break;
if (curr_pos_elem->value() != pos) {
input_event.type = InputEvent::SCREEN_DRAG;
input_event.screen_drag.index = index;
input_event.screen_drag.x = pos.x;
input_event.screen_drag.y = pos.y;
input_event.screen_drag.relative_x = pos.x - curr_pos_elem->value().x;
input_event.screen_drag.relative_y = pos.y - curr_pos_elem->value().y;
input->parse_input_event(input_event);
if (index == touch_mouse_index) {
mouse_event.type = InputEvent::MOUSE_MOTION;
mouse_event.mouse_motion.x = pos.x;
mouse_event.mouse_motion.y = pos.y;
mouse_event.mouse_motion.global_x = pos.x;
mouse_event.mouse_motion.global_y = pos.y;
input->set_mouse_pos(pos);
mouse_event.mouse_motion.relative_x = pos.x - last_mouse_pos.x;
mouse_event.mouse_motion.relative_y = pos.y - last_mouse_pos.y;
last_mouse_pos = pos;
input->parse_input_event(mouse_event);
}
2017-11-29 20:01:20 +00:00
curr_pos_elem->value() = pos;
}
} break;
}
}
}
XFreeEventData(x11_display, &event.xcookie);
2017-11-29 20:01:20 +00:00
#endif
2014-02-10 01:10:30 +00:00
switch (event.type) {
case Expose:
Main::force_redraw();
break;
2014-02-10 01:10:30 +00:00
case NoExpose:
minimized = true;
break;
case VisibilityNotify: {
XVisibilityEvent *visibility = (XVisibilityEvent *)&event;
minimized = (visibility->state == VisibilityFullyObscured);
} break;
case LeaveNotify: {
if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED)
main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT);
} break;
case EnterNotify: {
if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED)
main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER);
} break;
case FocusIn:
minimized = false;
main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
if (mouse_mode == MOUSE_MODE_CAPTURED) {
XGrabPointer(
x11_display, x11_window, True,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime);
}
2017-11-29 20:01:20 +00:00
#ifdef TOUCH_ENABLED
// Grab touch devices to avoid OS gesture interference
/*for (int i = 0; i < touch.devices.size(); ++i) {
2017-11-29 20:01:20 +00:00
XIGrabDevice(x11_display, touch.devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &touch.event_mask);
}*/
// Ensure touch mouse is unstuck
touch_mouse_index = -1;
num_touches = 0;
2017-11-29 20:01:20 +00:00
#endif
break;
case FocusOut:
main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
if (mouse_mode == MOUSE_MODE_CAPTURED) {
//dear X11, I try, I really try, but you never work, you do whathever you want.
XUngrabPointer(x11_display, CurrentTime);
}
2017-11-29 20:01:20 +00:00
#ifdef TOUCH_ENABLED
// Ungrab touch devices so input works as usual while we are unfocused
/*for (int i = 0; i < touch.devices.size(); ++i) {
2017-11-29 20:01:20 +00:00
XIUngrabDevice(x11_display, touch.devices[i], CurrentTime);
}*/
2017-11-29 20:01:20 +00:00
// Release every pointer to avoid sticky points
for (Map<int, Point2i>::Element *E = touch.state.front(); E; E = E->next()) {
InputEvent input_event;
input_event.type = InputEvent::SCREEN_TOUCH;
input_event.screen_touch.index = E->key();
input_event.screen_touch.x = E->get().x;
input_event.screen_touch.y = E->get().y;
input_event.screen_touch.pressed = false;
input->parse_input_event(input_event);
}
touch.state.clear();
#endif
break;
2014-02-10 01:10:30 +00:00
case ConfigureNotify:
/* call resizeGLScene only if our window-size changed */
2014-02-10 01:10:30 +00:00
if ((event.xconfigure.width == current_videomode.width) &&
(event.xconfigure.height == current_videomode.height))
break;
2016-03-08 23:00:52 +00:00
current_videomode.width = event.xconfigure.width;
current_videomode.height = event.xconfigure.height;
2014-02-10 01:10:30 +00:00
break;
case ButtonPress:
case ButtonRelease: {
/* exit in case of a mouse button press */
last_timestamp = event.xbutton.time;
if (mouse_mode == MOUSE_MODE_CAPTURED) {
event.xbutton.x = last_mouse_pos.x;
event.xbutton.y = last_mouse_pos.y;
}
2016-03-08 23:00:52 +00:00
InputEvent mouse_event;
mouse_event.ID = ++event_id;
mouse_event.type = InputEvent::MOUSE_BUTTON;
mouse_event.device = 0;
mouse_event.mouse_button.mod = get_key_modifier_state(event.xbutton.state);
mouse_event.mouse_button.button_mask = get_mouse_button_state(event.xbutton.state);
mouse_event.mouse_button.x = event.xbutton.x;
mouse_event.mouse_button.y = event.xbutton.y;
mouse_event.mouse_button.global_x = event.xbutton.x;
mouse_event.mouse_button.global_y = event.xbutton.y;
mouse_event.mouse_button.button_index = event.xbutton.button;
if (mouse_event.mouse_button.button_index == 2)
mouse_event.mouse_button.button_index = 3;
else if (mouse_event.mouse_button.button_index == 3)
mouse_event.mouse_button.button_index = 2;
2014-02-10 01:10:30 +00:00
mouse_event.mouse_button.pressed = (event.type == ButtonPress);
2014-02-10 01:10:30 +00:00
if (event.type == ButtonPress && event.xbutton.button == 1) {
2016-03-08 23:00:52 +00:00
uint64_t diff = get_ticks_usec() / 1000 - last_click_ms;
2014-02-10 01:10:30 +00:00
if (diff < 400 && Point2(last_click_pos).distance_to(Point2(event.xbutton.x, event.xbutton.y)) < 5) {
2016-03-08 23:00:52 +00:00
last_click_ms = 0;
last_click_pos = Point2(-100, -100);
mouse_event.mouse_button.doubleclick = true;
mouse_event.ID = ++event_id;
2016-03-08 23:00:52 +00:00
} else {
last_click_ms += diff;
last_click_pos = Point2(event.xbutton.x, event.xbutton.y);
}
2014-02-10 01:10:30 +00:00
}
input->parse_input_event(mouse_event);
} break;
case MotionNotify: {
// FUCK YOU X11 API YOU SERIOUSLY GROSS ME OUT
// YOU ARE AS GROSS AS LOOKING AT A PUTRID PILE
// OF POOP STICKING OUT OF A CLOGGED TOILET
// HOW THE FUCK I AM SUPPOSED TO KNOW WHICH ONE
// OF THE MOTION NOTIFY EVENTS IS THE ONE GENERATED
// BY WARPING THE MOUSE POINTER?
// YOU ARE FORCING ME TO FILTER ONE BY ONE TO FIND IT
// PLEASE DO ME A FAVOR AND DIE DROWNED IN A FECAL
// MOUNTAIN BECAUSE THAT'S WHERE YOU BELONG.
while (true) {
if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == current_videomode.width / 2 && event.xmotion.y == current_videomode.height / 2) {
//this is likely the warp event since it was warped here
center = Vector2(event.xmotion.x, event.xmotion.y);
break;
}
if (XPending(x11_display) > 0) {
XEvent tevent;
XPeekEvent(x11_display, &tevent);
if (tevent.type == MotionNotify) {
XNextEvent(x11_display, &event);
} else {
break;
}
} else {
break;
}
}
last_timestamp = event.xmotion.time;
2016-03-08 23:00:52 +00:00
// Motion is also simple.
// A little hack is in order
// to be able to send relative motion events.
Point2i pos(event.xmotion.x, event.xmotion.y);
2014-02-10 01:10:30 +00:00
if (mouse_mode == MOUSE_MODE_CAPTURED) {
2014-02-10 01:10:30 +00:00
#if 1
//Vector2 c = Point2i(current_videomode.width/2,current_videomode.height/2);
if (pos == Point2i(current_videomode.width / 2, current_videomode.height / 2)) {
//this sucks, it's a hack, etc and is a little inaccurate, etc.
//but nothing I can do, X11 sucks.
2014-02-10 01:10:30 +00:00
center = pos;
break;
}
2014-02-10 01:10:30 +00:00
Point2i new_center = pos;
pos = last_mouse_pos + (pos - center);
center = new_center;
do_mouse_warp = true;
2014-02-10 01:10:30 +00:00
#else
//Dear X11, thanks for making my life miserable
2014-02-10 01:10:30 +00:00
center.x = current_videomode.width / 2;
center.y = current_videomode.height / 2;
pos = last_mouse_pos + (pos - center);
if (pos == last_mouse_pos)
break;
XWarpPointer(x11_display, None, x11_window,
0, 0, 0, 0, (int)center.x, (int)center.y);
2014-02-10 01:10:30 +00:00
#endif
}
2015-02-12 14:58:00 +00:00
if (!last_mouse_pos_valid) {
2016-03-08 23:00:52 +00:00
last_mouse_pos = pos;
last_mouse_pos_valid = true;
}
2016-03-08 23:00:52 +00:00
Point2i rel = pos - last_mouse_pos;
InputEvent motion_event;
motion_event.ID = ++event_id;
motion_event.type = InputEvent::MOUSE_MOTION;
motion_event.device = 0;
motion_event.mouse_motion.mod = get_key_modifier_state(event.xmotion.state);
motion_event.mouse_motion.button_mask = get_mouse_button_state(event.xmotion.state);
motion_event.mouse_motion.x = pos.x;
motion_event.mouse_motion.y = pos.y;
input->set_mouse_pos(pos);
motion_event.mouse_motion.global_x = pos.x;
motion_event.mouse_motion.global_y = pos.y;
motion_event.mouse_motion.speed_x = input->get_mouse_speed().x;
motion_event.mouse_motion.speed_y = input->get_mouse_speed().y;
motion_event.mouse_motion.relative_x = rel.x;
motion_event.mouse_motion.relative_y = rel.y;
last_mouse_pos = pos;
// printf("rel: %d,%d\n", rel.x, rel.y );
input->parse_input_event(motion_event);
} break;
case KeyPress:
case KeyRelease: {
last_timestamp = event.xkey.time;
// key event is a little complex, so
// it will be handled in it's own function.
handle_key_event((XKeyEvent *)&event);
} break;
case SelectionRequest: {
XSelectionRequestEvent *req;
XEvent e, respond;
e = event;
req = &(e.xselectionrequest);
if (req->target == XA_STRING || req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||
req->target == XInternAtom(x11_display, "UTF8_STRING", 0)) {
CharString clip = OS::get_clipboard().utf8();
XChangeProperty(x11_display,
req->requestor,
req->property,
req->target,
8,
PropModeReplace,
(unsigned char *)clip.get_data(),
clip.length());
respond.xselection.property = req->property;
} else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) {
Atom data[2];
data[0] = XInternAtom(x11_display, "UTF8_STRING", 0);
data[1] = XA_STRING;
XChangeProperty(x11_display, req->requestor, req->property, req->target,
8, PropModeReplace, (unsigned char *)&data,
sizeof(data));
respond.xselection.property = req->property;
2014-02-10 01:10:30 +00:00
} else {
printf("No String %x\n",
(int)req->target);
respond.xselection.property = None;
}
respond.xselection.type = SelectionNotify;
respond.xselection.display = req->display;
respond.xselection.requestor = req->requestor;
respond.xselection.selection = req->selection;
respond.xselection.target = req->target;
respond.xselection.time = req->time;
XSendEvent(x11_display, req->requestor, 0, 0, &respond);
XFlush(x11_display);
} break;
2016-03-08 23:00:52 +00:00
case SelectionNotify:
2014-02-10 01:10:30 +00:00
if (event.xselection.target == requested) {
2014-02-10 01:10:30 +00:00
Property p = read_property(x11_display, x11_window, XInternAtom(x11_display, "PRIMARY", 0));
Vector<String> files = String((char *)p.data).split("\n", false);
for (int i = 0; i < files.size(); i++) {
files[i] = files[i].replace("file://", "").replace("%20", " ").strip_escapes();
}
main_loop->drop_files(files);
//Reply that all is well.
XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = x11_display;
m.window = xdnd_source_window;
m.message_type = xdnd_finished;
m.format = 32;
m.data.l[0] = x11_window;
m.data.l[1] = 1;
m.data.l[2] = xdnd_action_copy; //We only ever copy.
XSendEvent(x11_display, xdnd_source_window, False, NoEventMask, (XEvent *)&m);
}
break;
2014-02-10 01:10:30 +00:00
case ClientMessage:
2016-03-08 23:00:52 +00:00
if ((unsigned int)event.xclient.data.l[0] == (unsigned int)wm_delete)
main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) {
//File(s) have been dragged over the window, check for supported target (text/uri-list)
xdnd_version = (event.xclient.data.l[1] >> 24);
Window source = event.xclient.data.l[0];
bool more_than_3 = event.xclient.data.l[1] & 1;
if (more_than_3) {
Property p = read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));
requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);
} else
requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
} else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) {
//xdnd position event, reply with an XDND status message
//just depending on type of data for now
XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = event.xclient.data.l[0];
m.message_type = xdnd_status;
m.format = 32;
m.data.l[0] = x11_window;
m.data.l[1] = (requested != None);
m.data.l[2] = 0; //empty rectangle
m.data.l[3] = 0;
m.data.l[4] = xdnd_action_copy;
XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
XFlush(x11_display);
} else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) {
if (requested != None) {
xdnd_source_window = event.xclient.data.l[0];
if (xdnd_version >= 1)
XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, event.xclient.data.l[2]);
else
XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), x11_window, CurrentTime);
} else {
//Reply that we're not interested.
XClientMessageEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.display = event.xclient.display;
m.window = event.xclient.data.l[0];
m.message_type = xdnd_finished;
m.format = 32;
m.data.l[0] = x11_window;
m.data.l[1] = 0;
m.data.l[2] = None; //Failed.
XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m);
}
}
break;
default:
break;
2014-02-10 01:10:30 +00:00
}
}
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
XFlush(x11_display);
if (do_mouse_warp) {
XWarpPointer(x11_display, None, x11_window,
0, 0, 0, 0, (int)current_videomode.width / 2, (int)current_videomode.height / 2);
2015-02-12 14:58:00 +00:00
2016-03-08 23:00:52 +00:00
/*
2015-02-12 14:58:00 +00:00
Window root, child;
int root_x, root_y;
int win_x, win_y;
unsigned int mask;
XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );
printf("Root: %d,%d\n", root_x, root_y);
printf("Win: %d,%d\n", win_x, win_y);
*/
2014-02-10 01:10:30 +00:00
}
}
MainLoop *OS_X11::get_main_loop() const {
return main_loop;
}
void OS_X11::delete_main_loop() {
if (main_loop)
memdelete(main_loop);
main_loop = NULL;
2014-02-10 01:10:30 +00:00
}
void OS_X11::set_main_loop(MainLoop *p_main_loop) {
2014-02-10 01:10:30 +00:00
main_loop = p_main_loop;
2014-02-10 01:10:30 +00:00
input->set_main_loop(p_main_loop);
}
bool OS_X11::can_draw() const {
return !minimized;
};
void OS_X11::set_clipboard(const String &p_text) {
2014-02-10 01:10:30 +00:00
OS::set_clipboard(p_text);
XSetSelectionOwner(x11_display, XA_PRIMARY, x11_window, CurrentTime);
XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, CurrentTime);
};
static String _get_clipboard_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) {
2014-02-10 01:10:30 +00:00
String ret;
Atom type;
Atom selection = XA_PRIMARY;
int format, result;
unsigned long len, bytes_left, dummy;
unsigned char *data;
Window Sown = XGetSelectionOwner(x11_display, p_source);
2014-02-10 01:10:30 +00:00
if (Sown == x11_window) {
return p_internal_clipboard;
};
if (Sown != None) {
XConvertSelection(x11_display, p_source, target, selection,
x11_window, CurrentTime);
XFlush(x11_display);
2014-02-10 01:10:30 +00:00
while (true) {
XEvent event;
XNextEvent(x11_display, &event);
if (event.type == SelectionNotify && event.xselection.requestor == x11_window) {
break;
};
};
//
// Do not get any data, see how much data is there
//
XGetWindowProperty(x11_display, x11_window,
selection, // Tricky..
0, 0, // offset - len
0, // Delete 0==FALSE
AnyPropertyType, //flag
&type, // return type
&format, // return format
&len, &bytes_left, //that
&data);
2014-02-10 01:10:30 +00:00
// DATA is There
if (bytes_left > 0) {
result = XGetWindowProperty(x11_display, x11_window,
selection, 0, bytes_left, 0,
AnyPropertyType, &type, &format,
&len, &dummy, &data);
2014-02-10 01:10:30 +00:00
if (result == Success) {
ret.parse_utf8((const char *)data);
} else
printf("FAIL\n");
XFree(data);
2014-02-10 01:10:30 +00:00
}
}
return ret;
2016-07-21 19:11:34 +00:00
}
2014-02-10 01:10:30 +00:00
static String _get_clipboard(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) {
String ret;
Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);
if (utf8_atom != None) {
ret = _get_clipboard_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom);
}
if (ret == "") {
ret = _get_clipboard_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING);
}
return ret;
}
2014-02-10 01:10:30 +00:00
String OS_X11::get_clipboard() const {
String ret;
ret = _get_clipboard(XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, x11_display, OS::get_clipboard());
if (ret == "") {
ret = _get_clipboard(XA_PRIMARY, x11_window, x11_display, OS::get_clipboard());
};
return ret;
2016-07-21 19:11:34 +00:00
}
2014-02-10 01:10:30 +00:00
String OS_X11::get_name() {
return "X11";
}
Error OS_X11::shell_open(String p_uri) {
Error ok;
List<String> args;
args.push_back(p_uri);
ok = execute("/usr/bin/xdg-open", args, false);
if (ok == OK)
return OK;
ok = execute("gnome-open", args, false);
if (ok == OK)
return OK;
ok = execute("kde-open", args, false);
return ok;
}
String OS_X11::get_system_dir(SystemDir p_dir) const {
String xdgparam;
switch (p_dir) {
case SYSTEM_DIR_DESKTOP: {
xdgparam = "DESKTOP";
} break;
case SYSTEM_DIR_DCIM: {
xdgparam = "PICTURES";
} break;
case SYSTEM_DIR_DOCUMENTS: {
xdgparam = "DOCUMENTS";
} break;
case SYSTEM_DIR_DOWNLOADS: {
xdgparam = "DOWNLOAD";
} break;
case SYSTEM_DIR_MOVIES: {
xdgparam = "VIDEOS";
} break;
case SYSTEM_DIR_MUSIC: {
xdgparam = "MUSIC";
} break;
case SYSTEM_DIR_PICTURES: {
xdgparam = "PICTURES";
} break;
case SYSTEM_DIR_RINGTONES: {
xdgparam = "MUSIC";
} break;
}
String pipe;
List<String> arg;
arg.push_back(xdgparam);
Error err = const_cast<OS_X11 *>(this)->execute("/usr/bin/xdg-user-dir", arg, true, NULL, &pipe);
if (err != OK)
return ".";
return pipe.strip_edges();
}
2014-02-10 01:10:30 +00:00
void OS_X11::move_window_to_foreground() {
XRaiseWindow(x11_display, x11_window);
2014-02-10 01:10:30 +00:00
}
void OS_X11::set_cursor_shape(CursorShape p_shape) {
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
2014-02-10 01:10:30 +00:00
if (p_shape == current_cursor)
2014-02-10 01:10:30 +00:00
return;
if (mouse_mode == MOUSE_MODE_VISIBLE) {
if (cursors[p_shape] != None)
XDefineCursor(x11_display, x11_window, cursors[p_shape]);
else if (cursors[CURSOR_ARROW] != None)
XDefineCursor(x11_display, x11_window, cursors[CURSOR_ARROW]);
2014-02-10 01:10:30 +00:00
}
current_cursor = p_shape;
2014-02-10 01:10:30 +00:00
}
void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
if (p_cursor.is_valid()) {
Ref<ImageTexture> texture = p_cursor;
Ref<AtlasTexture> atlas_texture = p_cursor;
Size2 texture_size;
Rect2 atlas_rect;
if (!texture.is_valid() && atlas_texture.is_valid()) {
texture = atlas_texture->get_atlas();
atlas_rect.size.width = texture->get_width();
atlas_rect.size.height = texture->get_height();
atlas_rect.pos.x = atlas_texture->get_region().pos.x;
atlas_rect.pos.y = atlas_texture->get_region().pos.y;
texture_size.width = atlas_texture->get_region().size.x;
texture_size.height = atlas_texture->get_region().size.y;
} else {
texture_size.width = texture->get_width();
texture_size.height = texture->get_height();
}
ERR_FAIL_COND(!texture.is_valid());
ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
Image image = texture->get_data();
// Create the cursor structure
XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);
XcursorUInt image_size = texture_size.width * texture_size.height;
XcursorDim size = sizeof(XcursorPixel) * image_size;
cursor_image->version = 1;
cursor_image->size = size;
cursor_image->xhot = p_hotspot.x;
cursor_image->yhot = p_hotspot.y;
// allocate memory to contain the whole file
cursor_image->pixels = (XcursorPixel *)malloc(size);
for (XcursorPixel index = 0; index < image_size; index++) {
int row_index = floor(index / texture_size.width) + atlas_rect.pos.y;
int column_index = (index % int(texture_size.width)) + atlas_rect.pos.x;
if (atlas_texture.is_valid()) {
column_index = MIN(column_index, atlas_rect.size.width - 1);
row_index = MIN(row_index, atlas_rect.size.height - 1);
}
*(cursor_image->pixels + index) = image.get_pixel(column_index, row_index).to_ARGB32();
}
ERR_FAIL_COND(cursor_image->pixels == NULL);
// Save it for a further usage
cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);
if (p_shape == CURSOR_ARROW) {
XDefineCursor(x11_display, x11_window, cursors[p_shape]);
}
}
}
2014-02-10 01:10:30 +00:00
void OS_X11::release_rendering_thread() {
context_gl->release_current();
}
void OS_X11::make_rendering_thread() {
context_gl->make_current();
}
void OS_X11::swap_buffers() {
context_gl->swap_buffers();
}
void OS_X11::alert(const String &p_alert, const String &p_title) {
List<String> args;
args.push_back("-center");
args.push_back("-title");
args.push_back(p_title);
args.push_back(p_alert);
execute("/usr/bin/xmessage", args, true);
}
2014-02-10 01:10:30 +00:00
void OS_X11::set_icon(const Image &p_icon) {
2015-10-25 17:36:27 +00:00
Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);
2015-10-25 14:15:56 +00:00
2014-02-10 01:10:30 +00:00
if (!p_icon.empty()) {
Image img = p_icon;
2014-02-10 01:10:30 +00:00
img.convert(Image::FORMAT_RGBA);
int w = img.get_width();
int h = img.get_height();
// We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits
Vector<long> pd;
2014-02-10 01:10:30 +00:00
pd.resize(2 + w * h);
2014-02-10 01:10:30 +00:00
pd[0] = w;
pd[1] = h;
2014-02-10 01:10:30 +00:00
DVector<uint8_t>::Read r = img.get_data().read();
long *wr = &pd[2];
uint8_t const *pr = r.ptr();
2014-02-10 01:10:30 +00:00
for (int i = 0; i < w * h; i++) {
long v = 0;
// A R G B
v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];
*wr++ = v;
pr += 4;
2014-02-10 01:10:30 +00:00
}
XChangeProperty(x11_display, x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());
2014-02-10 01:10:30 +00:00
} else {
XDeleteProperty(x11_display, x11_window, net_wm_icon);
2014-02-10 01:10:30 +00:00
}
XFlush(x11_display);
}
void OS_X11::run() {
force_quit = false;
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
if (!main_loop)
return;
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
main_loop->init();
2016-03-08 23:00:52 +00:00
// uint64_t last_ticks=get_ticks_usec();
2016-03-08 23:00:52 +00:00
// int frames=0;
// uint64_t frame=0;
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
while (!force_quit) {
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
process_xevents(); // get rid of pending events
#ifdef JOYDEV_ENABLED
2015-12-18 05:12:53 +00:00
event_id = joystick->process_joysticks(event_id);
#endif
if (Main::iteration() == true)
2014-02-10 01:10:30 +00:00
break;
};
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
main_loop->finish();
}
bool OS_X11::is_joy_known(int p_device) {
return input->is_joy_mapped(p_device);
}
String OS_X11::get_joy_guid(int p_device) const {
return input->get_joy_guid_remapped(p_device);
}
void OS_X11::set_use_vsync(bool p_enable) {
if (context_gl)
return context_gl->set_use_vsync(p_enable);
}
bool OS_X11::is_vsync_enabled() const {
if (context_gl)
return context_gl->is_using_vsync();
return true;
}
void OS_X11::set_context(int p_context) {
XClassHint *classHint = NULL;
classHint = XAllocClassHint();
if (classHint) {
if (p_context == CONTEXT_EDITOR)
classHint->res_name = (char *)"Godot_Editor";
if (p_context == CONTEXT_PROJECTMAN)
classHint->res_name = (char *)"Godot_ProjectList";
classHint->res_class = (char *)"Godot";
XSetClassHint(x11_display, x11_window, classHint);
XFree(classHint);
}
}
2018-01-23 00:15:43 +00:00
OS::LatinKeyboardVariant OS_X11::get_latin_keyboard_variant() const {
XkbDescRec *xkbdesc = XkbAllocKeyboard();
ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY);
XkbGetNames(x11_display, XkbSymbolsNameMask, xkbdesc);
ERR_FAIL_COND_V(!xkbdesc->names, LATIN_KEYBOARD_QWERTY);
ERR_FAIL_COND_V(!xkbdesc->names->symbols, LATIN_KEYBOARD_QWERTY);
char *layout = XGetAtomName(x11_display, xkbdesc->names->symbols);
ERR_FAIL_COND_V(!layout, LATIN_KEYBOARD_QWERTY);
Vector<String> info = String(layout).split("+");
ERR_FAIL_INDEX_V(1, info.size(), LATIN_KEYBOARD_QWERTY);
if (info[1].find("colemak") != -1) {
return LATIN_KEYBOARD_COLEMAK;
} else if (info[1].find("qwertz") != -1) {
return LATIN_KEYBOARD_QWERTZ;
} else if (info[1].find("azerty") != -1) {
return LATIN_KEYBOARD_AZERTY;
} else if (info[1].find("qzerty") != -1) {
return LATIN_KEYBOARD_QZERTY;
} else if (info[1].find("dvorak") != -1) {
return LATIN_KEYBOARD_DVORAK;
} else if (info[1].find("neo") != -1) {
return LATIN_KEYBOARD_NEO;
}
return LATIN_KEYBOARD_QWERTY;
}
2014-02-10 01:10:30 +00:00
OS_X11::OS_X11() {
#ifdef RTAUDIO_ENABLED
AudioDriverManagerSW::add_driver(&driver_rtaudio);
#endif
#ifdef PULSEAUDIO_ENABLED
AudioDriverManagerSW::add_driver(&driver_pulseaudio);
#endif
#ifdef ALSA_ENABLED
AudioDriverManagerSW::add_driver(&driver_alsa);
#endif
if (AudioDriverManagerSW::get_driver_count() == 0) {
WARN_PRINT("No sound driver found... Defaulting to dummy driver");
AudioDriverManagerSW::add_driver(&driver_dummy);
}
2014-02-10 01:10:30 +00:00
minimized = false;
xim_style = 0L;
mouse_mode = MOUSE_MODE_VISIBLE;
#ifdef TOUCH_ENABLED
num_touches = 0;
touch_mouse_index = -1;
#endif
2016-07-21 19:11:34 +00:00
}
void OS_X11::disable_crash_handler() {
crash_handler.disable();
}
bool OS_X11::is_disable_crash_handler() const {
return crash_handler.is_disabled();
}