ca971bbfd8
This patch changes the top-level directory layout as a preparatory step for improving the tools for managing 3rd-party source codes. The rationale is described in the issue referenced below. Issue #1082
530 lines
11 KiB
C++
530 lines
11 KiB
C++
/*
|
|
* \brief Window-event tracker for the X Window System
|
|
* \author Norman Feske
|
|
* \date 2009-11-04
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2009-2013 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
/* Linux includes */
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
|
|
/* X11 includes */
|
|
#include <X11/Xlib.h>
|
|
#include <X11/extensions/Xdamage.h>
|
|
|
|
/* xev includes */
|
|
#include <xev_track/xev_track.h>
|
|
|
|
|
|
struct View
|
|
{
|
|
bool tracked; /* flag indicating that the view is in use */
|
|
Window xwin;
|
|
Window above;
|
|
int x, y, w, h, border;
|
|
|
|
/**
|
|
* Default constructor
|
|
*
|
|
* Make sure that the window is initially marked as free
|
|
*/
|
|
View() : tracked(false) { }
|
|
};
|
|
|
|
|
|
static View views[MAX_VIEWS];
|
|
static Window root;
|
|
static int xdamage_ev;
|
|
static Damage damage;
|
|
|
|
|
|
/***************
|
|
** Utilities **
|
|
***************/
|
|
|
|
/**
|
|
* Allocate view ID
|
|
*
|
|
* \return allocated view ID, or
|
|
* -1 if allocation failed
|
|
*/
|
|
static int alloc_view_id()
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAX_VIEWS; i++)
|
|
if (!views[i].tracked) break;
|
|
|
|
if (i == MAX_VIEWS)
|
|
return -1;
|
|
|
|
views[i].tracked = true;
|
|
return i;
|
|
}
|
|
|
|
|
|
/**
|
|
* Mark view ID as free
|
|
*/
|
|
static void release_view_id(int view_id)
|
|
{
|
|
views[view_id].tracked = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find view ID for a given X window ID
|
|
*/
|
|
static int find_view_id(Window xwin)
|
|
{
|
|
/* search for window with matchin xwin id */
|
|
for (int i = 0; i < MAX_VIEWS; i++)
|
|
if (views[i].xwin == xwin && views[i].tracked)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create new window
|
|
*
|
|
* \param position -1 .. at top,
|
|
* -2 .. background,
|
|
* otherwise window id of the neighbor behind the new
|
|
* window
|
|
*/
|
|
static void assign_window(int view_id, Window xwin, Display *dpy, int position)
|
|
{
|
|
/* sanity check */
|
|
if (view_id < 0) return;
|
|
|
|
View *view = &views[view_id];
|
|
|
|
view->xwin = xwin;
|
|
|
|
/* request position and size of new window */
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(dpy, xwin, &attr);
|
|
view->x = attr.x;
|
|
view->y = attr.y;
|
|
view->w = attr.width;
|
|
view->h = attr.height;
|
|
view->border = attr.border_width;
|
|
|
|
create_view(view_id);
|
|
|
|
if (position == -2) {
|
|
stack_view(view_id, -1, false);
|
|
set_background_view(view_id);
|
|
}
|
|
|
|
if (position == -1)
|
|
stack_view(view_id, -1, true);
|
|
|
|
if (position >= 0)
|
|
stack_view(view_id, position, true);
|
|
|
|
place_view(view_id, view->x, view->y, view->w + view->border*2,
|
|
view->h + view->border*2);
|
|
}
|
|
|
|
|
|
/**
|
|
* Scan all currently present windows
|
|
*/
|
|
static void scan_windows(Display *dpy, Window root)
|
|
{
|
|
Window dummy_w1, dummy_w2, *win_list;
|
|
unsigned int num_wins;
|
|
XQueryTree(dpy, root, &dummy_w1, &dummy_w2, &win_list, &num_wins);
|
|
|
|
for (unsigned i = 0; i < num_wins; i++) {
|
|
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(dpy, win_list[i], &attr);
|
|
|
|
if (attr.map_state != IsViewable)
|
|
continue;
|
|
|
|
int view_id = alloc_view_id();
|
|
if (view_id >= 0)
|
|
assign_window(view_id, win_list[i], dpy, -1);
|
|
}
|
|
XFree(win_list);
|
|
|
|
/* listen at the root window */
|
|
XSelectInput(dpy, root, SubstructureNotifyMask | PointerMotionMask);
|
|
}
|
|
|
|
|
|
/**
|
|
* Find view belonging to the window in front of the specified X window
|
|
*
|
|
* \return view ID, or
|
|
* -1 if no matching view exists
|
|
*/
|
|
static int find_view_in_front(Display *dpy, Window root, Window win)
|
|
{
|
|
Window dummy_w1, dummy_w2, *win_list;
|
|
unsigned int num_wins, i;
|
|
|
|
XQueryTree(dpy, root, &dummy_w1, &dummy_w2, &win_list, &num_wins);
|
|
|
|
/* find window in X window stack */
|
|
for (i = 0; i < num_wins; i++)
|
|
if (win_list[i] == win)
|
|
break;
|
|
|
|
/* skip current window */
|
|
i++;
|
|
|
|
/* find and return view belonging to the X window */
|
|
int curr;
|
|
for (; i < num_wins; i++)
|
|
if ((curr = find_view_id(win_list[i])))
|
|
return curr;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void get_pointer_pos(Display *dpy, int *mx, int *my)
|
|
{
|
|
Window dummy_win;
|
|
int dummy_int;
|
|
unsigned dummy_uint;
|
|
XQueryPointer(dpy, root, &dummy_win, &dummy_win,
|
|
mx, my, &dummy_int, &dummy_int, &dummy_uint);
|
|
}
|
|
|
|
|
|
struct Mouse_cursor_tracker
|
|
{
|
|
private:
|
|
|
|
enum { CURSOR_WIDTH = 20, CURSOR_HEIGHT = 20 };
|
|
int _x1, _y1, _x2, _y2;
|
|
bool _valid;
|
|
|
|
public:
|
|
|
|
Mouse_cursor_tracker() : _valid(false) { }
|
|
|
|
void reset(int x, int y)
|
|
{
|
|
_x1 = x - CURSOR_WIDTH;
|
|
_y1 = y - CURSOR_HEIGHT;
|
|
_x2 = x + CURSOR_WIDTH;
|
|
_y2 = y + CURSOR_HEIGHT;
|
|
_valid = false;
|
|
}
|
|
|
|
void track(int x, int y)
|
|
{
|
|
if (_x1 > x - CURSOR_WIDTH) _x1 = x - CURSOR_WIDTH;
|
|
if (_y1 > y - CURSOR_HEIGHT) _y1 = y - CURSOR_HEIGHT;
|
|
if (_x2 < x + CURSOR_WIDTH) _x2 = x + CURSOR_WIDTH;
|
|
if (_y2 < y + CURSOR_HEIGHT) _y2 = y + CURSOR_HEIGHT;
|
|
_valid = true;
|
|
}
|
|
|
|
bool bounding_box(int *x, int *y, int *w, int *h)
|
|
{
|
|
*x = _x1;
|
|
*y = _y1;
|
|
*w = _x2 - _x1 + 1;
|
|
*h = _y2 - _y1 + 1;
|
|
return _valid;
|
|
}
|
|
};
|
|
|
|
struct Mouse_cursor_tracker mouse_cursor_tracker;
|
|
|
|
|
|
/****************************
|
|
** Top-window enforcement **
|
|
****************************/
|
|
|
|
/*
|
|
* Some window managers do not raise a window that is already on top. This is
|
|
* bad because there may be overlay windows that are not known to the X window
|
|
* system but that cover the topmost X window. Thus, we want always to receive
|
|
* a top event. For this, we create a dedicated invisible window that stays
|
|
* always on top of all others. The topmost real X window is always the second.
|
|
* Therefore, the window manager thinks that this window can be topped and
|
|
* generates the desired event.
|
|
*/
|
|
static Window topwin;
|
|
|
|
|
|
static bool window_left_of_screen(Display *dpy, Window xwin)
|
|
{
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(dpy, xwin, &attr);
|
|
|
|
return (attr.x + attr.width <= 0);
|
|
}
|
|
|
|
|
|
enum {
|
|
MAGIC_WIN_X = 2000, MAGIC_WIN_Y = 2000,
|
|
MAGIC_WIN_W = 1, MAGIC_WIN_H = 1
|
|
};
|
|
|
|
|
|
/**
|
|
* Create magic window that stays always on top
|
|
*/
|
|
static void create_magic_topwin(Display *dpy, Window root)
|
|
{
|
|
XWindowChanges wincfg;
|
|
|
|
/* create magic window that stays always on top */
|
|
topwin = XCreateWindow(dpy, root,
|
|
MAGIC_WIN_X, MAGIC_WIN_Y, /* position */
|
|
MAGIC_WIN_W, MAGIC_WIN_H, /* size */
|
|
0, /* border */
|
|
CopyFromParent, /* depth */
|
|
InputOutput, /* class */
|
|
CopyFromParent, /* visual */
|
|
0, 0);
|
|
|
|
wincfg.x = MAGIC_WIN_X;
|
|
wincfg.y = MAGIC_WIN_Y;
|
|
XConfigureWindow(dpy, topwin, CWX | CWY , &wincfg);
|
|
XMapWindow(dpy, topwin);
|
|
wincfg.x = MAGIC_WIN_X;
|
|
wincfg.y = MAGIC_WIN_Y;
|
|
XConfigureWindow(dpy, topwin, CWX | CWY , &wincfg);
|
|
|
|
|
|
int view_id = alloc_view_id();
|
|
if (view_id >= 0)
|
|
assign_window(view_id, topwin, dpy, root);
|
|
}
|
|
|
|
|
|
/**
|
|
* Bring magic window in front of all others
|
|
*/
|
|
static void raise_magic_window(Display *dpy)
|
|
{
|
|
XRaiseWindow(dpy, topwin);
|
|
|
|
/*
|
|
* Some window managers tend to relocate existing windows on startup. So
|
|
* let's re-position the window to make sure that it remains invisible in
|
|
* such cases.
|
|
*/
|
|
XMoveWindow(dpy, topwin, -200, -200);
|
|
}
|
|
|
|
|
|
/**********************
|
|
** X event handling **
|
|
**********************/
|
|
|
|
static void handle_xwindow_event(Display *dpy, Window root, XEvent &ev)
|
|
{
|
|
int view_id, behind_id;
|
|
int x, y, w, h;
|
|
View *view;
|
|
|
|
switch (ev.type) {
|
|
|
|
case MotionNotify:
|
|
|
|
x = ev.xmotion.x_root;
|
|
y = ev.xmotion.y_root;
|
|
mouse_cursor_tracker.track(x, y);
|
|
break;
|
|
|
|
case ConfigureNotify:
|
|
|
|
if ((view_id = find_view_id(ev.xconfigure.window)) < 0)
|
|
break;
|
|
|
|
x = ev.xconfigure.x;
|
|
y = ev.xconfigure.y;
|
|
w = ev.xconfigure.width;
|
|
h = ev.xconfigure.height;
|
|
|
|
view = &views[view_id];
|
|
|
|
/*
|
|
* If window position and size keeps the same,
|
|
* we assume, the window has been topped.
|
|
*/
|
|
if (x == view->x && y == view->y && w == view->w && h == view->h) {
|
|
|
|
int behind_id = find_view_in_front(dpy, root, ev.xconfigure.window);
|
|
|
|
stack_view(view_id, behind_id, true);
|
|
|
|
if (!window_left_of_screen(dpy, ev.xconfigure.window) && config_force_top)
|
|
raise_magic_window(dpy);
|
|
|
|
} else {
|
|
|
|
/* keep track new window position */
|
|
view->x = x; view->y = y; view->w = w; view->h = h;
|
|
|
|
place_view(view_id, x, y, w + view->border*2, h + view->border*2);
|
|
}
|
|
break;
|
|
|
|
case Expose:
|
|
|
|
if ((view_id = find_view_id(ev.xconfigure.window)) < 0)
|
|
break;
|
|
|
|
stack_view(view_id, -1, true);
|
|
break;
|
|
|
|
case UnmapNotify:
|
|
|
|
if ((view_id = find_view_id(ev.xconfigure.window)) < 0)
|
|
break;
|
|
|
|
/* avoid destroying a view twice */
|
|
if (!views[view_id].tracked)
|
|
break;
|
|
|
|
destroy_view(view_id);
|
|
release_view_id(view_id);
|
|
break;
|
|
|
|
case MapNotify:
|
|
|
|
if ((view_id = find_view_id(ev.xconfigure.window)) >= 0) {
|
|
printf("MapRequest: window already present - view ID %d\n", view_id);
|
|
break;
|
|
}
|
|
|
|
behind_id = find_view_in_front(dpy, root, ev.xconfigure.window);
|
|
|
|
/*
|
|
* Idea: Call XQueryTree to obtain the position where the new
|
|
* window is located in the window stack.
|
|
*
|
|
*/
|
|
|
|
view_id = alloc_view_id();
|
|
if (view_id >= 0)
|
|
assign_window(view_id, ev.xconfigure.window, dpy, behind_id);
|
|
|
|
if (!window_left_of_screen(dpy, ev.xconfigure.window) && config_force_top)
|
|
raise_magic_window(dpy);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void handle_xdamage_event(Display *dpy, XEvent &ev)
|
|
{
|
|
if (ev.type != XDamageNotify + xdamage_ev)
|
|
return;
|
|
|
|
static XserverRegion region = XFixesCreateRegion (dpy, 0, 0);
|
|
static XserverRegion part = XFixesCreateRegion (dpy, 0, 0);
|
|
|
|
XDamageNotifyEvent *dev = (XDamageNotifyEvent *)&ev;
|
|
|
|
XFixesSetRegion(dpy, part, &dev->area, 1);
|
|
XFixesUnionRegion(dpy, region, region, part);
|
|
XFlush(dpy);
|
|
|
|
XRectangle *rects;
|
|
int nrects;
|
|
|
|
nrects = 0;
|
|
rects = XFixesFetchRegion (dpy, region, &nrects);
|
|
|
|
for (int i = 0; i < nrects; i++)
|
|
refresh(rects[i].x, rects[i].y, rects[i].width, rects[i].height);
|
|
|
|
/* clear collected damage region from damage */
|
|
XDamageSubtract (dpy, damage, region, None);
|
|
|
|
/* empty collected region */
|
|
XFixesSetRegion (dpy, region, 0, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Error handler that is called on xlib errors
|
|
*/
|
|
static int x_error_handler(Display *dpy, XErrorEvent *r)
|
|
{
|
|
printf("Error: x_error_handler called\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**********************
|
|
** Public interface **
|
|
**********************/
|
|
|
|
void xev_track_handle_event(Display *dpy, XEvent &ev)
|
|
{
|
|
handle_xwindow_event(dpy, root, ev);
|
|
handle_xdamage_event(dpy, ev);
|
|
}
|
|
|
|
|
|
void xev_track_handle_cursor(Display *dpy)
|
|
{
|
|
int x = 0, y = 0, w = 0, h = 0;
|
|
static int old_mx, old_my;
|
|
int new_mx, new_my;
|
|
|
|
get_pointer_pos(dpy, &new_mx, &new_my);
|
|
if (new_mx != old_mx || new_my != old_my)
|
|
mouse_cursor_tracker.track(new_mx, new_my);
|
|
|
|
if (mouse_cursor_tracker.bounding_box(&x, &y, &w, &h))
|
|
refresh(x, y, w, h);
|
|
|
|
mouse_cursor_tracker.reset(old_mx, old_my);
|
|
mouse_cursor_tracker.track(new_mx, new_my);
|
|
|
|
old_mx = new_mx, old_my = new_my;
|
|
}
|
|
|
|
|
|
bool xev_track_init(Display *dpy)
|
|
{
|
|
XSetErrorHandler(x_error_handler);
|
|
|
|
int screen = DefaultScreen(dpy);
|
|
root = RootWindow(dpy, screen);
|
|
|
|
int error;
|
|
if (!XDamageQueryExtension (dpy, &xdamage_ev, &error)) {
|
|
printf("Error: Could not query Xdamage extension\n");
|
|
return false;
|
|
}
|
|
|
|
damage = XDamageCreate(dpy, root, XDamageReportBoundingBox);
|
|
|
|
if (config_force_top)
|
|
create_magic_topwin(dpy, root);
|
|
|
|
/* create background view */
|
|
int bg_view_id = alloc_view_id();
|
|
if (bg_view_id >= 0)
|
|
assign_window(bg_view_id, root, dpy, -2);
|
|
|
|
/* retrieve information about currently present windows */
|
|
scan_windows(dpy, root);
|
|
|
|
return true;
|
|
}
|