gambas-source-code/gb.desktop.x11/systray/xembed.c

537 lines
18 KiB
C

/* -------------------------------
* vim:tabstop=4:shiftwidth=4
* xembed.c
* Tue, 24 Aug 2004 12:05:38 +0700
* -------------------------------
* XEMBED protocol implementation
* -------------------------------*/
/* Currently broken:
* - XEMBED_{ACTIVATE,REGISTER,UNREGISTER}_ACCELERATOR
* - XEMBED_REQUEST_FOCUS */
#include <X11/X.h>
#include <X11/Xmd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "common.h"
#include "debug.h"
#include "xembed.h"
#include "xutils.h"
#include "wmh.h"
#include "list.h"
/* Internal return codes */
#define XEMBED_RESULT_OK 0
#define XEMBED_RESULT_UNSUPPORTED 1
#define XEMBED_RESULT_X11ERROR 2
/* XEMBED messages */
#define XEMBED_EMBEDDED_NOTIFY 0
#define XEMBED_WINDOW_ACTIVATE 1
#define XEMBED_WINDOW_DEACTIVATE 2
#define XEMBED_REQUEST_FOCUS 3
#define XEMBED_FOCUS_IN 4
#define XEMBED_FOCUS_OUT 5
#define XEMBED_FOCUS_NEXT 6
#define XEMBED_FOCUS_PREV 7
/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
#define XEMBED_MODALITY_ON 10
#define XEMBED_MODALITY_OFF 11
#define XEMBED_REGISTER_ACCELERATOR 12
#define XEMBED_UNREGISTER_ACCELERATOR 13
#define XEMBED_ACTIVATE_ACCELERATOR 14
/* Details for XEMBED_FOCUS_IN */
#define XEMBED_FOCUS_CURRENT 0
#define XEMBED_FOCUS_FIRST 1
#define XEMBED_FOCUS_LAST 2
/* Modifiers field for XEMBED_REGISTER_ACCELERATOR */
#define XEMBED_MODIFIER_SHIFT (1 << 0)
#define XEMBED_MODIFIER_CONTROL (1 << 1)
#define XEMBED_MODIFIER_ALT (1 << 2)
#define XEMBED_MODIFIER_SUPER (1 << 3)
#define XEMBED_MODIFIER_HYPER (1 << 4)
/* Flags for XEMBED_ACTIVATE_ACCELERATOR */
#define XEMBED_ACCELERATOR_OVERLOADED (1 << 0)
/* Directions for focusing */
#define XEMBED_DIRECTION_DEFAULT 0
#define XEMBED_DIRECTION_UP_DOWN 1
#define XEMBED_DIRECTION_LEFT_RIGHT 2
/* Flags for _XEMBED_INFO */
#define XEMBED_MAPPED (1 << 0)
/* Structure to hold XEMBED accelerator data */
struct XEMBEDAccel {
struct XEMBEDAccel *next, *prev;
int overloaded; /* Is this accelerator overloaded? */
long id; /* Accelerator Id */
long symb; /* Symbol */
long mods; /* Modifiers */
};
/* Shortcuts for sending XEMBED messages */
#define xembed_send_msg(dpy, dst, timestamp, msg, detail, data1, data2) \
x11_send_client_msg32(dpy, dst, dst, tray_data.xembed_data.xa_xembed, timestamp, msg, detail, data1, data2)
#define xembed_send_embedded_notify(dpy, src, dst, timestamp) \
xembed_send_msg(dpy, dst, timestamp, XEMBED_EMBEDDED_NOTIFY, 0, src, 0)
#define xembed_send_window_activate(dpy, dst, timestamp) \
xembed_send_msg(dpy, dst, timestamp, XEMBED_WINDOW_ACTIVATE, 0, 0, 0)
#define xembed_send_window_deactivate(dpy, dst, timestamp) \
xembed_send_msg(dpy, dst, timestamp, XEMBED_WINDOW_DEACTIVATE, 0, 0, 0)
#define xembed_send_focus_in(dpy, dst, focus, timestamp) \
xembed_send_msg(dpy, dst, timestamp, XEMBED_FOCUS_IN, focus, 0, 0)
#define xembed_send_focus_out(dpy, dst, timestamp) \
xembed_send_msg(dpy, dst, timestamp, XEMBED_FOCUS_OUT, 0, 0, 0)
#define xembed_send_activate_accelerator(dpy, dst, timestamp, id, overloaded) \
xembed_send_msg(dpy, dst, timestamp, XEMBED_ACTIVATE_ACCELERATOR, id, overloaded, 0)
/* Retrieve XEMBED data for the given icon */
int xembed_retrieve_data(struct TrayIcon *ti);
/* Post icon XEMBED data to window property */
int xembed_post_data(struct TrayIcon *ti);
/* Returns the next icon in tab chain */
struct TrayIcon *xembed_next();
/* Returns the previous icon in tab chain */
struct TrayIcon *xembed_prev();
/* XEMBED event handler */
int xembed_process_kbd_event(XKeyEvent xkey);
/* Register new XEMBED accelerator */
void xembed_add_accel(long id, long symb, long mods);
/* Delete previously registered XEMBED accelerator */
void xembed_del_accel(long id);
/* Activate previously registered XEMBED accelerator */
void xembed_act_accel(struct XEMBEDAccel *accel);
/* Switch XEMBED focus to the specified icon */
void xembed_switch_focus_to(struct TrayIcon *tgt, long focus);
/* Broadcast the focus change to all icons */
void xembed_track_focus_change(int activate);
/* Process XEMBED message */
void xembed_message(XClientMessageEvent ev);
/* Tries to request focus from WM */
void xembed_request_focus_from_wm();
void xembed_init()
{
/* 1. Initialize data structures */
tray_data.xembed_data.window_has_focus = False;
tray_data.xembed_data.focus_requested = False;
tray_data.xembed_data.current = NULL;
tray_data.xembed_data.accels = NULL;
tray_data.xembed_data.timestamp = CurrentTime;
tray_data.xembed_data.xa_xembed = XInternAtom(tray_data.dpy, "_XEMBED", False);
tray_data.xembed_data.xa_xembed_info = XInternAtom(tray_data.dpy, "_XEMBED_INFO", False);
/* 2. Create focus proxy (see XEMBED spec) */
tray_data.xembed_data.focus_proxy =
XCreateSimpleWindow(tray_data.dpy, tray_data.tray, -1, -1, 1, 1, 0, 0, 0);
XSelectInput(tray_data.dpy, tray_data.xembed_data.focus_proxy, FocusChangeMask | KeyPressMask | KeyReleaseMask);
XMapRaised(tray_data.dpy, tray_data.xembed_data.focus_proxy);
if (!x11_ok()) DIE(("could not create focus proxy\n"));
LOG_TRACE(("created focus proxy, wid=0x%x\n", tray_data.xembed_data.focus_proxy));
}
void xembed_handle_event(XEvent ev)
{
switch (ev.type) {
case FocusOut:
/* Broadcast that the focus has left tray window */
LOG_TRACE(("FocusOut 0x%x\n", ev.xfocus.window));
if (ev.xfocus.window == tray_data.xembed_data.focus_proxy)
xembed_track_focus_change(False);
break;
case ClientMessage:
/* Handle XEMBED-related messages */
if (ev.xclient.message_type == tray_data.xembed_data.xa_xembed) {
xembed_message(ev.xclient);
} else if (ev.xclient.message_type == tray_data.xa_tray_opcode) {
/* we peek at _NET_SYSTEM_TRAY_OPCODE messages
* to obtain proper timestamp for embedding */
tray_data.xembed_data.timestamp = ev.xclient.data.l[0];
if (tray_data.xembed_data.timestamp == CurrentTime)
tray_data.xembed_data.timestamp = x11_get_server_timestamp(tray_data.dpy, tray_data.tray);
} else if (ev.xclient.message_type == tray_data.xa_wm_protocols &&
ev.xclient.data.l[0] == tray_data.xa_wm_take_focus &&
tray_data.xembed_data.focus_requested)
{
XSetInputFocus(tray_data.dpy, tray_data.xembed_data.focus_proxy, RevertToParent, ev.xclient.data.l[1]);
if (!x11_ok()) DIE_IE(("Could not set focus to XEMBED focus proxy\n"));
LOG_TRACE(("focus set to focus proxy\n"));
xembed_track_focus_change(True);
tray_data.xembed_data.focus_requested = False;
}
break;
case KeyRelease:
case KeyPress:
/* Propagate key events to currently focused icon */
tray_data.xembed_data.timestamp = ev.xkey.time;
if (ev.type == KeyRelease && xembed_process_kbd_event(ev.xkey))
break;
if (tray_data.xembed_data.current != NULL) {
int rc;
LOG_TRACE(("current icon accepts_focus: %d\n", tray_data.xembed_data.current->is_xembed_accepts_focus));
rc = XSendEvent(tray_data.dpy, tray_data.xembed_data.current->wid, False, NoEventMask, &ev);
if (!x11_ok() || rc == 0) {
tray_data.xembed_data.current->is_invalid = True;
return;
}
LOG_TRACE(("sent key event to 0x%x\n", tray_data.xembed_data.current->wid));
}
break;
}
}
int xembed_check_support(struct TrayIcon *ti)
{
int rc = xembed_retrieve_data(ti);
ti->is_xembed_supported = (rc == XEMBED_RESULT_OK);
return rc != XEMBED_RESULT_X11ERROR;
}
int xembed_get_mapped_state(struct TrayIcon *ti)
{
/* It is OK to retrieve data each time this function
* is called, since there is some overhead only during
* initialization, when xembed_retrieve_data is called 2
* times in a row(). */
int rc = xembed_retrieve_data(ti);
if (ti->is_xembed_supported && rc == XEMBED_RESULT_OK)
return ((ti->xembed_data[1] & XEMBED_MAPPED) != 0);
else {
ti->is_xembed_supported = False;
ti->is_invalid = (rc == XEMBED_RESULT_X11ERROR);
return False;
}
}
int xembed_set_mapped_state(struct TrayIcon *ti, int state)
{
if (!ti->is_xembed_supported) return FAILURE;
if (state)
ti->xembed_data[1] |= XEMBED_MAPPED;
else
ti->xembed_data[1] &= ~XEMBED_MAPPED;
return xembed_post_data(ti);
}
int xembed_embed(struct TrayIcon *ti)
{
/* if XEMBED is not supported, do nothing */
if (!ti->is_xembed_supported) return SUCCESS;
/* By default, consider that all icons accept focus */
ti->is_xembed_accepts_focus = True;
/* Send notification */
if (!xembed_send_embedded_notify(tray_data.dpy, tray_data.tray, ti->wid, tray_data.xembed_data.timestamp))
return FAILURE;
ti->xembed_last_timestamp = tray_data.xembed_data.timestamp;
ti->xembed_last_msgid = XEMBED_EMBEDDED_NOTIFY;
if (tray_data.xembed_data.current == NULL) {
/* No icon has focus. Set focus to this one */
if (!xembed_send_focus_in(tray_data.dpy, ti->wid, XEMBED_FOCUS_FIRST, tray_data.xembed_data.timestamp))
return FAILURE;
tray_data.xembed_data.current = ti;
}
/* Send activation message if tray window has focus */
if (tray_data.xembed_data.window_has_focus)
return xembed_send_window_activate(tray_data.dpy, ti->wid, tray_data.xembed_data.timestamp);
return SUCCESS;
}
int xembed_unembed(struct TrayIcon *ti)
{
struct TrayIcon *tmp;
tray_data.xembed_data.timestamp = x11_get_server_timestamp(tray_data.dpy, tray_data.tray);
if (ti == tray_data.xembed_data.current) {
/* Currently focused icon is being unembedded,
* move focus to the next icon. */
tmp = xembed_next();
if (tmp == ti || tmp->is_xembed_accepts_focus == False) {
xembed_switch_focus_to(NULL, 0);
} else {
xembed_switch_focus_to(tmp, XEMBED_FOCUS_FIRST);
}
}
return SUCCESS;
}
/*********** implementation level ***************/
void xembed_switch_focus_to(struct TrayIcon *tgt, long focus)
{
/* 1. Send "focus out" message to the currently focused icon */
if (tray_data.xembed_data.current != NULL) {
LOG_TRACE(("XEMBED focus was removed from icon 0x%x (pointer %p)\n", tray_data.xembed_data.current->wid, tray_data.xembed_data.current));
xembed_send_focus_out(tray_data.dpy, tray_data.xembed_data.current->wid, tray_data.xembed_data.timestamp);
}
/* 2. Send "focus in" message to the icon to be focused */
if (tgt != NULL) {
xembed_send_focus_in(tray_data.dpy, tgt->wid, focus, tray_data.xembed_data.timestamp);
LOG_TRACE(("XEMBED focus was set to icon 0x%x (pointer %p)\n", tgt->wid, tgt));
} else {
LOG_TRACE(("XEMBED focus was unset\n"));
}
tray_data.xembed_data.current = tgt;
}
static int activate = 0;
int broadcast_activate_msg(struct TrayIcon *ti)
{
if (activate)
xembed_send_window_activate(tray_data.dpy, ti->wid, tray_data.xembed_data.timestamp);
else
xembed_send_window_deactivate(tray_data.dpy, ti->wid, tray_data.xembed_data.timestamp);
return NO_MATCH;
}
void xembed_track_focus_change(int in)
{
if (tray_data.xembed_data.window_has_focus == in) return;
tray_data.xembed_data.window_has_focus = in;
activate = in;
icon_list_forall(&broadcast_activate_msg);
LOG_TRACE(("XEMBED focus is %s\n", in ? "ON" : "OFF"));
}
void xembed_message(XClientMessageEvent ev)
{
long msgid;
LOG_TRACE(("this is an _XEMBED message, window: 0x%x, timestamp: %u, opcode: %u, \ndetail: 0x%x, data1 = 0x%x, data2 = 0x%x\n",
ev.window, ev.data.l[0], ev.data.l[1], ev.data.l[2], ev.data.l[3], ev.data.l[4]));
#ifdef DEBUG
if (tray_data.xembed_data.current != NULL)
LOG_TRACE(("XEMBED focus is in window 0x%x (pointer %p)\n", tray_data.xembed_data.current->wid, tray_data.xembed_data.current));
else
LOG_TRACE(("XEMBED focus is unset\n"));
#endif
if (ev.window != tray_data.tray) {
LOG_TRACE(("inoring _XEMBED message to some other window\n"));
return;
}
/* Update timestamp if necessary */
if (ev.data.l[0] == CurrentTime)
ev.data.l[0] = x11_get_server_timestamp(tray_data.dpy, tray_data.tray);
tray_data.xembed_data.timestamp = ev.data.l[0];
msgid = ev.data.l[1];
LOG_TRACE(("_XEMBED message %u\n", msgid));
switch (msgid) {
case XEMBED_REQUEST_FOCUS:
xembed_request_focus_from_wm();
break;
case XEMBED_FOCUS_NEXT:
case XEMBED_FOCUS_PREV:
if (tray_data.xembed_data.current != NULL) {
struct TrayIcon *old_focus, *new_focus;
old_focus = tray_data.xembed_data.current;
new_focus = (msgid == XEMBED_FOCUS_NEXT) ?
xembed_next():
xembed_prev();
if (new_focus->is_xembed_accepts_focus) {
/* If the last message for the new focus target was focus_{next,prev} and
* it has the same timestamp as the current message, it is likely that
* the corresponding icon does not want to be focused at all. So mark it
* as not accepting focus. */
if (new_focus->xembed_last_timestamp == tray_data.xembed_data.timestamp &&
(new_focus->xembed_last_msgid == XEMBED_FOCUS_NEXT ||
new_focus->xembed_last_msgid == XEMBED_FOCUS_PREV))
{
new_focus->is_xembed_accepts_focus = False;
new_focus = False;
}
old_focus->xembed_last_timestamp = tray_data.xembed_data.timestamp;
old_focus->xembed_last_msgid = msgid;
} else
new_focus = NULL;
xembed_switch_focus_to(new_focus, (msgid == XEMBED_FOCUS_NEXT) ?
XEMBED_FOCUS_FIRST :
XEMBED_FOCUS_LAST);
}
break;
case XEMBED_REGISTER_ACCELERATOR:
xembed_add_accel(ev.data.l[2], ev.data.l[3], ev.data.l[4]);
break;
case XEMBED_UNREGISTER_ACCELERATOR:
xembed_del_accel(ev.data.l[2]);
break;
default:
LOG_TRACE(("Unhandled _XEMBED message, id = %d\n", ev.data.l[1]));
break;
}
}
void xembed_add_accel(long id, long symb, long mods)
{
struct XEMBEDAccel *xaccel, *tmp;
xaccel = (struct XEMBEDAccel *) malloc(sizeof(struct XEMBEDAccel));
if (xaccel == NULL) {
LOG_ERR_OOM(("Could not register new XEMBED accelerator\n"));
return;
}
xaccel->id = id;
xaccel->symb = symb;
xaccel->mods = mods;
xaccel->overloaded = 0;
/* Check if there are already registered accelerators that are overloaded
* by this one */
for (tmp = tray_data.xembed_data.accels; tmp != NULL; tmp = tmp->next)
if (tmp->symb == symb && tmp->mods == mods) {
xaccel->overloaded++;
tmp->overloaded++;
}
LIST_ADD_ITEM(tray_data.xembed_data.accels, xaccel);
LOG_TRACE(("added new XEMBED accelerator: id=0x%x, sym=0x%x, mods=0x%x, overloaded=%d\n",
id, symb, mods, xaccel->overloaded));
}
void xembed_del_accel(long id)
{
struct XEMBEDAccel *tmp, *tgt = NULL;
for (tmp = tray_data.xembed_data.accels; tmp != NULL; tmp = tmp->next)
if (tmp->id == id) {
tgt = tmp;
return;
}
if (tgt == NULL) {
LOG_TRACE(("refusing to remove unregistered XEMBED accelerator\n"));
return;
}
/* Update overloaded status of the remaining accelerators */
for (tmp = tray_data.xembed_data.accels; tmp != NULL; tmp = tmp->next)
if (tmp->symb == tgt->symb && tmp->mods == tgt->mods)
tmp->overloaded--;
LIST_DEL_ITEM(tray_data.xembed_data.accels, tgt);
LOG_TRACE(("removed XEMBED accelator id=0x%x", tgt->id));
free(tgt);
}
static struct XEMBEDAccel *cur_accel;
int xembed_act_accel_helper(struct TrayIcon *ti)
{
xembed_send_activate_accelerator(tray_data.dpy,
ti->wid,
tray_data.xembed_data.timestamp,
cur_accel->id, cur_accel->overloaded ? 1 : 0);
return NO_MATCH;
}
void xembed_act_accel(struct XEMBEDAccel *accel)
{
LOG_TRACE(("activating XEMBED accelerator: id=0x%x (symb=0x%x, mods=0x%x)\n", accel->id, accel->symb, accel->mods));
cur_accel = accel;
icon_list_forall(&xembed_act_accel_helper);
}
int xembed_process_kbd_event(XKeyEvent xkey)
{
struct XEMBEDAccel *tmp;
int hits = 0;
KeySym keysym;
static char buf[20];
XLookupString(&xkey, buf, 20, &keysym, NULL);
LOG_TRACE(("Key event (type=%d) with keycode=0x%x, symb=0x%x, state=0x%x\n", xkey.type, xkey.keycode, keysym, xkey.state));
for (tmp = tray_data.xembed_data.accels; tmp != NULL; tmp = tmp->next)
if (tmp->symb == keysym && tmp->mods == xkey.state) {
xembed_act_accel(tmp);
hits = 1;
}
return hits;
}
struct TrayIcon *xembed_next()
{
struct TrayIcon *tmp, *blocker;
tmp = tray_data.xembed_data.current != NULL ? tray_data.xembed_data.current : NULL;
blocker = tmp != NULL ? tmp : icon_list_next(NULL);
do {
tmp = icon_list_next(tmp);
} while ((!tmp->is_xembed_supported || !tmp->is_xembed_accepts_focus) && tmp != blocker);
return tmp;
}
struct TrayIcon *xembed_prev()
{
struct TrayIcon *tmp, *blocker;
tmp = tray_data.xembed_data.current != NULL ? tray_data.xembed_data.current : NULL;
blocker = tmp != NULL ? tmp : icon_list_prev(tmp);
do {
tmp = icon_list_prev(tmp);
} while ((!tmp->is_xembed_supported || !tmp->is_xembed_accepts_focus) && tmp != blocker);
return tmp;
}
int xembed_retrieve_data(struct TrayIcon *ti)
{
Atom act_type;
int act_fmt;
unsigned long nitems, bytesafter, *data;
unsigned char *tmpdata;
int rc;
/* NOTE: x11_get_win_prop32 is not used since we need to distinguish between
* X11 errors and absence of the property */
x11_ok();
rc = XGetWindowProperty(tray_data.dpy,
ti->wid,
tray_data.xembed_data.xa_xembed_info,
0,
2,
False,
tray_data.xembed_data.xa_xembed_info,
&act_type,
&act_fmt,
&nitems,
&bytesafter,
&tmpdata);
if (rc != Success)
return XEMBED_RESULT_X11ERROR;
rc = (act_type == tray_data.xembed_data.xa_xembed_info && nitems == 2);
if (rc) {
data = (unsigned long*) tmpdata;
ti->xembed_data[0] = data[0];
ti->xembed_data[1] = data[1];
}
if (nitems && tmpdata != NULL) XFree(tmpdata);
return rc ? XEMBED_RESULT_OK : XEMBED_RESULT_UNSUPPORTED;
}
int xembed_post_data(struct TrayIcon *ti)
{
if (!ti->is_xembed_supported) return XEMBED_RESULT_UNSUPPORTED;
XChangeProperty(tray_data.dpy,
ti->wid,
tray_data.xembed_data.xa_xembed_info,
tray_data.xembed_data.xa_xembed_info,
32,
PropModeReplace,
(unsigned char *) ti->xembed_data,
2);
return x11_ok() ? XEMBED_RESULT_OK : XEMBED_RESULT_X11ERROR;
}
void xembed_request_focus_from_wm()
{
if (!tray_data.is_reparented) {
x11_send_client_msg32(tray_data.dpy,
DefaultRootWindow(tray_data.dpy),
tray_data.tray,
XInternAtom(tray_data.dpy, "_NET_ACTIVE_WINDOW", True),
1, /* Request is from application */
x11_get_server_timestamp(tray_data.dpy, tray_data.tray), /* Timestamp */
0, /* None window is focused current (?) */
0, /* Unused */
0);/* Unused */
tray_data.xembed_data.focus_requested = True;
}
}