gambas 09a0573e61 Replace my old sourceforge mail address by the new one.
[CONFIGURATION]
Replace my old sourceforge mail address by the new one.
2018-02-12 02:53:46 +01:00

836 lines
19 KiB
C

/***************************************************************************
x11.c
(c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
***************************************************************************/
#define __X11_C
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "main.h"
#include "x11.h"
#include "c_x11.h"
#include "systray/systray.h"
#define MAX_WINDOW_PROP 16
Atom X11_atom_net_wm_state;
Atom X11_atom_net_wm_state_above;
Atom X11_atom_net_wm_state_below;
Atom X11_atom_net_wm_state_stays_on_top;
Atom X11_atom_net_wm_state_skip_taskbar;
Atom X11_atom_net_wm_window_type;
Atom X11_atom_net_wm_window_type_normal;
Atom X11_atom_net_wm_window_type_utility;
Atom X11_atom_net_wm_desktop;
Atom X11_atom_net_wm_user_time;
Atom X11_atom_net_current_desktop;
Atom X11_atom_net_workarea = None;
Atom X11_atom_motif_wm_hints = None;
Atom X11_atom_net_system_tray = None;
Atom X11_UTF8_STRING;
bool X11_ready = FALSE;
Display *X11_display = NULL;
#define _display X11_display
Window X11_root = 0;
#define _root X11_root
bool X11_event_filter_enabled = FALSE;
static bool _atom_init = FALSE;
static bool _has_test_extension = FALSE;
static bool _init_keycode = FALSE;
static int _min_keycode, _max_keycode, _keysyms_per_keycode;
static KeySym *_keycode_map = NULL;
static XModifierKeymap *_modifier_map = NULL;
static KeyCode *_shift_keycode = NULL;
static KeyCode *_alt_gr_keycode = NULL;
typedef
struct {
int count;
Atom atoms[MAX_WINDOW_PROP];
bool changed;
}
X11_PROPERTY;
static X11_PROPERTY _window_prop;
static X11_PROPERTY _window_save[2];
static char *_property_value = NULL;
static GB_FUNCTION _x11_property_notify_func;
static GB_FUNCTION _x11_configure_notify_func;
static void init_atoms()
{
if (_atom_init)
return;
//fprintf(stderr, "init_atom: display = %p\n", _display);
X11_atom_net_current_desktop = XInternAtom(_display, "_NET_CURRENT_DESKTOP", True);
//fprintf(stderr, "init_atom: #1\n");
X11_atom_net_wm_state = XInternAtom(_display, "_NET_WM_STATE", True);
X11_atom_net_wm_state_above = XInternAtom(_display, "_NET_WM_STATE_ABOVE", True);
X11_atom_net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", True);
X11_atom_net_wm_state_stays_on_top = XInternAtom(_display, "_NET_WM_STATE_STAYS_ON_TOP", True);
X11_atom_net_wm_state_skip_taskbar = XInternAtom(_display, "_NET_WM_STATE_SKIP_TASKBAR", True);
X11_atom_net_wm_desktop = XInternAtom(_display, "_NET_WM_DESKTOP", True);
X11_atom_net_wm_window_type = XInternAtom(_display, "_NET_WM_WINDOW_TYPE", True);
X11_atom_net_wm_window_type_normal = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_NORMAL", True);
X11_atom_net_wm_window_type_utility = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_UTILITY", True);
X11_atom_net_wm_user_time = XInternAtom(_display, "_NET_WM_USER_TIME", True);
X11_UTF8_STRING = XInternAtom(X11_display, "UTF8_STRING", True);
_atom_init = TRUE;
}
#define PROPERTY_START_READ 1024
#define PROPERTY_NEXT_READ 1024
#if 0
// On 64 bits OS, format 32 are actually long, i.e. int padded to 64 bits!
#if OS_64BITS
if (format == 32)
{
int *p = (int *)_property_value;
for (i = 0; i < count; i++)
p[i] = *((long *)data + i);
}
else
#endif
{
memcpy(_property_value, (char *)data, count * size);
}
#endif
static size_t sizeof_format(int format)
{
return format == 32 ? sizeof(long) : (format == 16 ? sizeof(short) : sizeof(char));
}
char *X11_get_property(Window wid, Atom prop, Atom *type, int *format, int *pcount)
{
uchar *data;
unsigned long count;
unsigned long after;
unsigned long offset;
int size, offset_size;
*pcount = 0;
if (XGetWindowProperty(_display, wid, prop, 0, PROPERTY_START_READ / sizeof(int32_t),
False, AnyPropertyType, type, format,
&count, &after, &data) != Success)
return NULL;
*pcount += count;
size = sizeof_format(*format);
offset_size = *format == 32 ? sizeof(int32_t) : ( *format == 16 ? sizeof(short) : 1 );
//fprintf(stderr, "X11_get_property: format = %d size = %d count = %ld after = %ld\n", *format, size, count, after);
GB.FreeString(&_property_value);
_property_value = GB.NewString((char *)data, count * size);
XFree(data);
offset = count * offset_size / sizeof(int32_t);
while (after)
{
//fprintf(stderr, "X11_get_property: offset = %ld read = %ld\n", offset, Min(after, PROPERTY_NEXT_READ) / sizeof(int32_t));
if (XGetWindowProperty(_display, wid, prop, offset, Min(after, PROPERTY_NEXT_READ) / sizeof(int32_t),
False, AnyPropertyType, type, format,
&count, &after, &data) != Success)
return NULL;
*pcount += count;
offset += count * offset_size / sizeof(int32_t);
//fprintf(stderr, "X11_get_property: format = %d size = %d count = %ld after = %ld next offset = %ld\n", *format, size, count, after, offset);
_property_value = GB.AddString(_property_value, (char *)data, count * size);
XFree(data);
}
return _property_value;
}
#if 0
static void get_property(Window wid, Atom prop, int maxlength, unsigned char **data, unsigned long *count)
{
Atom type;
int format;
unsigned long after;
XGetWindowProperty(_display, wid, prop, 0, maxlength / 4,
False, AnyPropertyType, &type, &format,
count, &after, data);
}
#endif
static char *get_property(Window wid, Atom prop, int *count)
{
Atom type;
int format;
return X11_get_property(wid, prop, &type, &format, count);
}
Atom X11_get_property_type(Window wid, Atom prop, int *format)
{
uchar *data = NULL;
unsigned long count;
unsigned long after;
Atom type;
if (XGetWindowProperty(X11_display, wid, prop, 0, PROPERTY_START_READ / sizeof(int32_t),
False, AnyPropertyType, &type, format,
&count, &after, &data) != Success)
return None;
XFree(data);
return type;
}
#if 0
#if OS_64BITS
long padded_data[count];
int i;
if (format == 32)
{
for (i = 0; i < count; i++)
padded_data[i] = ((int *)data)[i];
}
data = padded_data;
#endif
#endif
void X11_set_property(Window wid, Atom prop, Atom type, int format, void *data, int count)
{
XChangeProperty(X11_display, wid, prop, type, format, PropModeReplace, (uchar *)data, count);
}
Atom X11_intern_atom(const char *name, bool create)
{
int val = atoi(name);
if (val)
return (Atom)val;
else
return XInternAtom(X11_display, name, !create);
}
static void load_window_state(Window win, Atom prop)
{
int length;
char *data;
_window_prop.count = 0;
_window_prop.changed = FALSE;
data = get_property(win, prop, &length);
if (length > MAX_WINDOW_PROP)
length = MAX_WINDOW_PROP;
_window_prop.count = length;
if (data)
memcpy(_window_prop.atoms, data, length * sizeof(Atom));
}
static void save_window_state(Window win, Atom prop)
{
if (_window_prop.changed)
{
//fprintf(stderr, "XChangeProperty: %ld %ld\n", (long)win, (long)prop);
XChangeProperty(_display, win, prop, XA_ATOM, 32, PropModeReplace, (unsigned char *)_window_prop.atoms, _window_prop.count);
}
}
static bool has_window_state(Atom prop)
{
int i;
for (i = 0; i < _window_prop.count; i++)
{
if (_window_prop.atoms[i] == prop)
return TRUE;
}
return FALSE;
}
static void set_window_state(Atom prop)
{
if (has_window_state(prop))
return;
if (_window_prop.count == MAX_WINDOW_PROP)
{
fprintf(stderr, "X11: set_window_state: Too many properties in window\n");
return;
}
_window_prop.atoms[_window_prop.count++] = prop;
_window_prop.changed = TRUE;
}
static void clear_window_state(Atom prop)
{
int i;
for (i = 0; i < _window_prop.count; i++)
{
if (_window_prop.atoms[i] == prop)
{
_window_prop.count--;
for (; i < _window_prop.count; i++)
_window_prop.atoms[i] = _window_prop.atoms[i + 1];
_window_prop.changed = TRUE;
return;
}
}
}
bool X11_do_init()
{
int event_base, error_base, major_version, minor_version;
if (X11_ready)
return FALSE;
GB.Component.GetInfo("DISPLAY", POINTER(&_display));
_root = DefaultRootWindow(_display);
X11_ready = _display != NULL;
if (!X11_ready)
{
fprintf(stderr, "WARNING: X11_init() has failed\n");
return TRUE;
}
init_atoms();
_has_test_extension = XTestQueryExtension(_display, &event_base, &error_base, &major_version, &minor_version);
return FALSE;
}
void X11_exit()
{
if (_keycode_map)
XFree(_keycode_map);
if (_modifier_map)
XFreeModifiermap(_modifier_map);
if (_property_value)
GB.FreeString(&_property_value);
}
void X11_send_client_message(Window dest, Window window, Atom message, char *data, int format, int count)
{
XEvent e;
int mask = (SubstructureRedirectMask | SubstructureNotifyMask);
//fprintf(stderr, "X11_send_client_message: dest = %ld window = %ld message = %ld format = %d count = %d\n", dest, window, message, format, count);
e.xclient.type = ClientMessage;
e.xclient.message_type = message;
e.xclient.display = X11_display;
e.xclient.window = window;
e.xclient.format = format;
memset(&e.xclient.data.l[0], 0, sizeof(long) * 5);
if (data)
{
count *= sizeof_format(format);
if (count > (sizeof(long) * 5))
count = sizeof(long) * 5;
memcpy(&e.xclient.data.l[0], data, count);
//fprintf(stderr, "%ld %ld %ld %ld %ld\n", e.xclient.data.l[0], e.xclient.data.l[1], e.xclient.data.l[2], e.xclient.data.l[3], e.xclient.data.l[4]);
}
XSendEvent(X11_display, dest, False, mask, &e);
XFlush(X11_display);
}
void X11_window_change_property(Window window, bool visible, Atom property, bool set)
{
XEvent e;
long mask = (SubstructureRedirectMask | SubstructureNotifyMask);
if (visible)
{
e.xclient.type = ClientMessage;
e.xclient.message_type = X11_atom_net_wm_state;
e.xclient.display = _display;
e.xclient.window = window;
e.xclient.format = 32;
e.xclient.data.l[0] = set ? 1 : 0;
e.xclient.data.l[1] = property;
e.xclient.data.l[2] = 0;
e.xclient.data.l[3] = 0;
e.xclient.data.l[4] = 0;
XSendEvent(_display, _root, False, mask, &e);
XFlush(X11_display);
}
else
{
load_window_state(window, X11_atom_net_wm_state);
if (set)
set_window_state(property);
else
clear_window_state(property);
save_window_state(window, X11_atom_net_wm_state);
}
}
bool X11_window_has_property(Window window, Atom property)
{
load_window_state(window, X11_atom_net_wm_state);
return has_window_state(property);
}
void X11_sync(void)
{
XSync(_display, False);
}
void X11_window_save_properties(Window window)
{
load_window_state(window, X11_atom_net_wm_state);
_window_save[0] = _window_prop;
//load_window_state(window, X11_atom_net_wm_window_type);
//_window_save[1] = _window_prop;
}
void X11_window_restore_properties(Window window)
{
_window_prop = _window_save[0];
save_window_state(window, X11_atom_net_wm_state);
//_window_prop = _window_save[1];
//save_window_state(window, X11_atom_net_wm_window_type);
}
#define SYSTEM_TRAY_REQUEST_DOCK 0
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
#define OPCODE "_NET_SYSTEM_TRAY_OPCODE"
void X11_window_dock(Window window)
{
Window xmanager=None;
XClientMessageEvent ev;
Atom OpCodeAtom;
Screen *xscreen;
char buf[256];
Atom selection_atom;
buf[0]=0;
xscreen = DefaultScreenOfDisplay(_display);
sprintf(buf,"_NET_SYSTEM_TRAY_S%d",XScreenNumberOfScreen(xscreen));
selection_atom = XInternAtom(_display, buf, 0);
XGrabServer(_display);
xmanager = XGetSelectionOwner(_display, selection_atom);
if (xmanager != None)
XSelectInput(_display, xmanager, StructureNotifyMask);
XUngrabServer(_display);
XFlush(_display);
/***********************************************
Dock Tray Icon
************************************************/
OpCodeAtom = XInternAtom(_display, OPCODE, 0);
ev.type = ClientMessage;
ev.window = xmanager;
ev.message_type = OpCodeAtom;
ev.format = 32;
ev.data.l[0] = 0;
ev.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
ev.data.l[2] = window;
ev.data.l[3] = 0;
ev.data.l[4] = 0;
XSendEvent(_display, xmanager, 0, NoEventMask, (XEvent *)&ev);
XSync(_display, 0);
}
void X11_window_startup(Window window, int x, int y, int w, int h)
{
XSizeHints s;
s.flags = USPosition | PPosition | USSize | PSize;
s.x = x;
s.y = y;
s.width = w;
s.height = h;
XSetWMNormalHints(_display, window, &s);
}
void X11_find_windows(Window **window_list, int *count)
{
static Atom _net_client_list = 0;
if (!_net_client_list)
_net_client_list = XInternAtom(_display, "_NET_CLIENT_LIST", True);
*window_list = (Window *)get_property(_root, _net_client_list, count);
}
void X11_get_window_title(Window window, char **result, int *length)
{
static Atom _net_wm_name = 0;
if (!_net_wm_name)
_net_wm_name = XInternAtom(_display, "_NET_WM_NAME", True);
*result = get_property(window, _net_wm_name, length);
}
void X11_get_window_class(Window window, char **result, int *length)
{
*result = get_property(window, XA_WM_CLASS, length);
}
void X11_get_window_role(Window window, char **result, int *length)
{
static Atom wm_window_role = (Atom)0;
if (!wm_window_role)
wm_window_role = XInternAtom(_display, "WM_WINDOW_ROLE", True);
*result = get_property(window, wm_window_role, length);
}
// Makes a tool window
void X11_set_window_tool(Window window, int tool, Window parent)
{
load_window_state(window, X11_atom_net_wm_window_type);
if (tool)
{
set_window_state(X11_atom_net_wm_window_type_utility);
clear_window_state(X11_atom_net_wm_window_type_normal);
if (parent)
XSetTransientForHint(_display, window, parent);
}
else
{
clear_window_state(X11_atom_net_wm_window_type_utility);
set_window_state(X11_atom_net_wm_window_type_normal);
}
save_window_state(window, X11_atom_net_wm_window_type);
}
int X11_get_window_tool(Window window)
{
load_window_state(window, X11_atom_net_wm_window_type);
return has_window_state(X11_atom_net_wm_window_type_utility);
}
// Set window desktop
void X11_window_set_desktop(Window window, bool visible, int desktop)
{
XEvent e;
long mask = (SubstructureRedirectMask | SubstructureNotifyMask);
if (visible)
{
e.xclient.type = ClientMessage;
e.xclient.message_type = X11_atom_net_wm_desktop;
e.xclient.display = _display;
e.xclient.window = window;
e.xclient.format = 32;
e.xclient.data.l[0] = desktop;
e.xclient.data.l[1] = 1;
e.xclient.data.l[2] = 0;
e.xclient.data.l[3] = 0;
e.xclient.data.l[4] = 0;
XSendEvent(_display, _root, False, mask, &e);
XFlush(_display);
}
else
{
XChangeProperty(_display, window, X11_atom_net_wm_desktop, XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)&desktop, 1);
XFlush(_display);
}
}
int X11_window_get_desktop(Window window)
{
int length;
char *data = NULL;
int desktop = 0;
data = get_property(window, X11_atom_net_wm_desktop, &length);
if (data)
desktop = *((int *)data);
return desktop;
}
int X11_get_current_desktop()
{
int length;
char *data;
int desktop = 0;
data = get_property(_root, X11_atom_net_current_desktop, &length);
if (data)
desktop = *((int *)data);
return desktop;
}
static void init_keycode()
{
int i, j; //, k;
KeyCode *pm, *p;
if (_init_keycode)
return;
XDisplayKeycodes(_display, &_min_keycode, &_max_keycode);
_keycode_map = XGetKeyboardMapping(_display, _min_keycode, _max_keycode - _min_keycode + 1, &_keysyms_per_keycode);
_modifier_map = XGetModifierMapping(_display);
p = _modifier_map->modifiermap;
for (i = 0; i < 8; i++)
{
pm = p;
for (j = 0; j < _modifier_map->max_keypermod; j++)
{
//for (k = 0; k < 3; k++)
switch (XkbKeycodeToKeysym(_display, *p, 0, 0))
{
case XK_Shift_L: _shift_keycode = pm; break;
case XK_Mode_switch: _alt_gr_keycode = pm; break;
}
p++;
}
}
//fprintf(stderr, "SHIFT: %d ALTGR: %d\n", _shift_keycode[0], _alt_gr_keycode[0]);
_init_keycode = TRUE;
}
static void send_modifiers(KeyCode *codes, bool press)
{
int i;
for (i = 0; i < _modifier_map->max_keypermod; i++)
{
if (codes[i])
XTestFakeKeyEvent(_display, codes[i], press, CurrentTime);
}
}
static void handle_modifier(KeyCode code, KeySym keysym, bool press)
{
KeySym *sym;
int i;
sym = &_keycode_map[(code - _min_keycode) * _keysyms_per_keycode];
for (i = 0; i < _keysyms_per_keycode; i++)
{
if (keysym == sym[i])
{
//fprintf(stderr, "0x%04X: %d\n", keysym, i);
break;
}
}
switch(i)
{
case 1:
send_modifiers(_shift_keycode, press);
break;
case 2:
send_modifiers(_alt_gr_keycode, press);
break;
case 3:
send_modifiers(_shift_keycode, press);
send_modifiers(_alt_gr_keycode, press);
break;
//default:
// fprintf(stderr, "0x%04X: ?\n", keysym);
}
}
char *X11_send_key(char *key, bool press)
{
KeySym keysym;
KeyCode code;
if (!_has_test_extension)
return "No XTEST extension";
if (!_init_keycode)
init_keycode();
//fprintf(stderr, "X11_send_key: '%s' %d\n", key, XStringToKeysym(key));
if (strlen(key) == 1)
{
if (*key == '\n')
keysym = XK_Return;
else if (*key == '\t')
keysym = XK_Tab;
else if (*key >= ' ' || *key < 0)
keysym = (int)*key & 0xFF;
else
keysym = NoSymbol;
}
else
keysym = XStringToKeysym(key);
if (keysym == NoSymbol)
return "Unknown key";
code = XKeysymToKeycode(_display, keysym);
if (code)
{
if (press)
handle_modifier(code, keysym, TRUE);
XTestFakeKeyEvent(_display, code, press, CurrentTime);
if (press)
handle_modifier(code, keysym, FALSE);
}
usleep(1000);
return NULL;
}
void X11_enable_event_filter(bool enable)
{
static int count = 0;
void (*set_event_filter)(void *) = NULL;
if (enable)
count++;
else
count--;
GB.Component.GetInfo("SET_EVENT_FILTER", POINTER(&set_event_filter));
if (set_event_filter)
(*set_event_filter)(count ? X11_event_filter : NULL);
}
void X11_event_filter(XEvent *e)
{
static bool init = FALSE;
if (!init)
{
GB_CLASS startup = GB.Application.StartupClass();
GB.GetFunction(&_x11_property_notify_func, (void *)startup, "X11_PropertyNotify", "ii", "");
GB.GetFunction(&_x11_configure_notify_func, (void *)startup, "X11_ConfigureNotify", "iiiii", "");
init = TRUE;
}
if (e->type == PropertyNotify && GB_FUNCTION_IS_VALID(&_x11_property_notify_func))
{
GB.Push(2, GB_T_INTEGER, e->xany.window, GB_T_INTEGER, e->xproperty.atom);
GB.Call(&_x11_property_notify_func, 2, TRUE);
}
else if (e->type == ConfigureNotify && GB_FUNCTION_IS_VALID(&_x11_configure_notify_func))
{
GB.Push(5, GB_T_INTEGER, e->xany.window,
GB_T_INTEGER, e->xconfigure.x,
GB_T_INTEGER, e->xconfigure.y,
GB_T_INTEGER, e->xconfigure.width,
GB_T_INTEGER, e->xconfigure.height);
GB.Call(&_x11_configure_notify_func, 5, TRUE);
}
WATCHER_event_filter(e);
SYSTRAY_event_filter(e);
}
void X11_get_window_geometry(Window win, int *wx, int *wy, int *ww, int *wh)
{
Window p;
int transx, transy;
XWindowAttributes wattr;
*wx = *wy = *ww = *wh = 0;
if (!XTranslateCoordinates(_display, win, _root, 0, 0, &transx, &transy, &p))
return;
if (!XGetWindowAttributes(_display, win, &wattr))
return;
*wx = transx - wattr.border_width;
*wy = transy - wattr.border_width;
*ww = wattr.width + wattr.border_width * 2;
*wh = wattr.height + wattr.border_width * 2;
}