blob: 986ce8fbb4418fed9f4fdcd42ad436875234c0b3 [file] [log] [blame]
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/X.h>
#include <dlfcn.h>
#include <sys/utsname.h>
#include <string.h>
#include "print_events.h"
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <elf.h>
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
// Define this to prevent events from being faked.
//#undef NO_FAKING
//#define DEBUG_PRINTOUTS
#ifdef DEBUG_PRINTOUTS
FILE* g_out_stream = 0;
#define LOG(...) if (g_out_stream != NULL) { fprintf(g_out_stream, __VA_ARGS__); fflush(g_out_stream); }
#define OPEN_LOGGING_FILE { g_out_stream = fopen("/tmp/x_ignore_focus_log.txt", "a+"); }
#define CLOSE_LOGGING_FILE { fclose(g_out_stream); g_out_stream = NULL; }
#else
// This is to prevent compiler warning for unused variables.
void do_nothing(const char* fmt, ...) {}
#define LOG(...) do_nothing(__VA_ARGS__)
#define OPEN_LOGGING_FILE ;
#define CLOSE_LOGGING_FILE ;
#endif
int g_library_inited = FALSE;
struct _FocusKeepStatus {
Window active_window;
Window new_window;
int start_switch_window;
int start_close_window;
int during_switch;
int during_close;
int should_steal_focus;
int encountered_focus_in_event;
int active_window_from_close;
};
typedef struct _FocusKeepStatus FocusKeepStatus;
void init_focus_keep_struct(FocusKeepStatus* stat)
{
stat->active_window = 0;
stat->new_window = 0;
stat->start_switch_window = FALSE;
stat->start_close_window = FALSE;
stat->during_switch = FALSE;
stat->during_close = FALSE;
stat->should_steal_focus = FALSE;
// This boolean is for remembering if we already had a FocusIn event and
// never re-send that event as well, not to break clients which expect to get
// FocusOut before FocusIn
stat->encountered_focus_in_event = FALSE;
// This remembers if the active was learnt due to a close
stat->active_window_from_close = FALSE;
};
Window get_active_window(FocusKeepStatus* stat)
{
return stat->active_window;
}
int is_focus_out(XEvent* ev)
{
return (ev->type == FocusOut);
}
int is_focus_in(XEvent* ev)
{
return (ev->type == FocusIn);
}
int is_reparent_notify(XEvent* ev)
{
return (ev->type == ReparentNotify);
}
int is_destroy_notify(XEvent* ev)
{
return (ev->type == DestroyNotify);
}
Window extract_window_id(XEvent* ev);
struct {
Window window_id;
Window* related_windows;
} g_cached_xquerytree;
void init_cached_xquerytree()
{
g_cached_xquerytree.window_id = 0;
g_cached_xquerytree.related_windows = 0;
}
// Performing XQueryTree after UnmapNotify for some of the
// windows will cause a crash. Cache to prevent it.
int cache_xquery_result(Display* dpy, Window for_win) {
Window root_win = 0;
Window parent_win = 0;
Window* childs_list = NULL;
unsigned int num_childs = 0;
int k = 0;
if ((g_cached_xquerytree.window_id == for_win) &&
(g_cached_xquerytree.related_windows != NULL)) {
return TRUE;
}
LOG("Invoking XQueryTree for window %#lx\n", for_win);
int queryRes = XQueryTree(dpy, for_win, &root_win,
&parent_win, &childs_list, &num_childs);
if (queryRes == 0) {
LOG("XQueryTree failed, rc=%d\n", queryRes);
return FALSE;
}
if (g_cached_xquerytree.related_windows != NULL) {
free(g_cached_xquerytree.related_windows);
g_cached_xquerytree.related_windows = NULL;
}
int numRelatedWindows = (1 /* parent_win */ +
1 /* actual win */ + num_childs + 1 /* NULL */);
g_cached_xquerytree.related_windows = malloc(sizeof(Window) * numRelatedWindows);
LOG("Allocated at address %p , numRelWindows: %d\n",
g_cached_xquerytree.related_windows, numRelatedWindows);
int relatedWinsIndex = 0;
g_cached_xquerytree.related_windows[relatedWinsIndex++] = parent_win;
g_cached_xquerytree.related_windows[relatedWinsIndex++] = for_win;
if ((num_childs > 0) && (childs_list != NULL)) {
for (k = 0; k < num_childs; k++) {
g_cached_xquerytree.related_windows[relatedWinsIndex++] = childs_list[k];
}
XFree(childs_list);
childs_list = NULL;
}
g_cached_xquerytree.related_windows[relatedWinsIndex] = 0;
g_cached_xquerytree.window_id = for_win;
return TRUE;
}
int lookup_in_xquery_cache(Window ev_win)
{
int ret_val = FALSE;
int k = 0;
if (g_cached_xquerytree.related_windows == NULL) {
LOG("related_windows is NULL, cache is inconsistent.\n");
return FALSE;
}
while ((g_cached_xquerytree.related_windows[k] != 0) && (!ret_val)) {
if (g_cached_xquerytree.related_windows[k] == ev_win) {
ret_val = TRUE;
}
k++;
}
return ret_val;
}
int window_ids_difference(Window win_one, Window win_two)
{
return (abs(win_one - win_two));
}
int event_on_active_or_adj_window(Display* dpy, XEvent* ev, Window active_win)
{
Window ev_win;
int ret_val = FALSE;
ev_win = extract_window_id(ev);
// This is probably also essential as on focus in events on new windows
// XQueryTree should not be called yet.
if (active_win == ev_win) {
return TRUE;
}
// "Obviously" related windows - ID of active window
// and event_window differ by 1. By performing this check first,
// we avoid calling XQueryTree which causes a segfault
// if the window queried is being closed.
if (abs(active_win - ev_win) <= 1) {
ret_val = TRUE;
} else {
if (cache_xquery_result(dpy, active_win)) {
ret_val = lookup_in_xquery_cache(ev_win);
}
}
return ret_val;
}
#define MAX_BUFFER_SIZE (256)
void identify_switch_situation(FocusKeepStatus* stat)
{
if (stat->start_switch_window || stat->start_close_window) {
// In the middle of a window switch.
Window old_active = get_active_window(stat);
stat->active_window = 0;
stat->during_switch = TRUE;
if (stat->start_close_window) {
stat->during_close = TRUE;
}
LOG("Window switching detected, active was: %#lx close: %d\n",
old_active, stat->during_close);
// Reset the the flags.
stat->start_switch_window = FALSE;
stat->start_close_window = FALSE;
}
}
void set_active_window(FocusKeepStatus* stat, XEvent* ev)
{
stat->active_window = extract_window_id(ev);
if (stat->during_close) {
stat->active_window_from_close = TRUE;
} else {
stat->active_window_from_close = FALSE;
}
stat->encountered_focus_in_event = FALSE;
stat->during_switch = FALSE;
stat->start_switch_window = FALSE;
stat->start_close_window = FALSE;
LOG("Setting Active Window due to FocusIn: %#lx (from close: %d)\n",
get_active_window(stat), stat->active_window_from_close);
}
void identify_new_window_situation(FocusKeepStatus* stat, XEvent* ev)
{
Window new_win = extract_window_id(ev);
assert(is_reparent_notify(ev));
if (get_active_window(stat) != 0) {
stat->new_window = new_win;
LOG("New window being created: %#lx\n", stat->new_window);
} else {
LOG("Reparent notify for window: %#lx, but no active.\n", new_win);
}
}
void identify_active_destroyed(FocusKeepStatus* stat, XEvent* ev)
{
assert(is_destroy_notify(ev));
if (extract_window_id(ev) == get_active_window(stat)) {
LOG("Active window: %#lx is destroyed!\n", get_active_window(stat));
stat->active_window = 0;
}
}
void steal_focus_back_if_needed(FocusKeepStatus* stat, Display* dpy)
{
if ((stat->should_steal_focus) && (get_active_window(stat) != 0)) {
stat->should_steal_focus = FALSE;
if ((!stat->during_close) || (stat->active_window_from_close)) {
LOG("Stealing focus back to %#lx\n", get_active_window(stat));
stat->new_window = 0;
XSetInputFocus(dpy, get_active_window(stat), RevertToParent, CurrentTime);
// Allow a focus in event to flow again to the window considered
// active.
stat->encountered_focus_in_event = FALSE;
} else {
LOG("Not stealing focus back. During close: %d Active from close: %d.\n",
stat->during_close, stat->active_window_from_close);
// Set during_close to false here - This is the point where the state
// transition is done - specifically, we consider the entire close
// process to be completed.
stat->during_close = FALSE;
}
}
}
int should_discard_focus_out_event(FocusKeepStatus* stat, Display* dpy,
XEvent *ev)
{
int ret_val = FALSE;
if (is_focus_out(ev) == FALSE) {
return FALSE;
}
const int detail = ev->xfocus.detail;
if (stat->new_window != 0) {
/*
if (!(event_on_active_or_adj_window(dpy, ev, stat->new_window)
|| event_on_active_or_adj_window(dpy, ev, get_active_window(stat)))) {
LOG( "ERROR - Event on window %#lx, which is neither new nor active.\n",
extract_window_id(ev));
} else */ {
LOG("Event on new/active (%#lx) during new window creation, allowing.",
extract_window_id(ev));
LOG(" New: %#lx Active: %#lx\n", stat->new_window, stat->active_window);
}
return FALSE;
}
if (event_on_active_or_adj_window(dpy, ev, get_active_window(stat))) {
// If moving ownership between sub-windows of the same Firefox window.
if ((detail == NotifyAncestor) || (detail == NotifyInferior)) {
// Allow this one.
LOG("Focus will move to ancestor / inferior (%d). Allowing.\n", detail);
stat->encountered_focus_in_event = FALSE;
} else {
// Disallow transfer of focus to outside windows.
if (!stat->active_window_from_close) {
ret_val = TRUE;
} else {
LOG("FocusOut event, but active window from close. Not discarding.\n");
}
}
} else {
LOG("Got Focus out event on window %#lx but active window is %#lx\n",
extract_window_id(ev), get_active_window(stat));
}
return ret_val;
}
int should_discard_focus_in_event(FocusKeepStatus* stat, Display* dpy,
XEvent *ev)
{
int ret_val = FALSE;
if (is_focus_in(ev) == FALSE) {
return FALSE;
}
// Event not on active window - It's either on a new window currently being
// created or on a different firefox one. On the first case, it will
// be allowed through, but blocked on the second case.
if (!event_on_active_or_adj_window(dpy, ev, get_active_window(stat))) {
LOG("Got Focus in event on window %#lx but active window is %#lx\n",
extract_window_id(ev), get_active_window(stat));
if (stat->new_window != 0) {
// If we are in the process of a new window creation, do not ignore
// this focus in event - allow it both for the new window
// and for a child window of it. However, if this is a focus in
// event for a child window (not the new window itself), then
// steal focus back from it afterwards.
ret_val = FALSE;
Window curr_win = extract_window_id(ev);
if (curr_win == stat->new_window) {
LOG("FocusIn event on new window - allowing.\n");
} else {
//if (event_on_active_or_adj_window(dpy, ev, stat->new_window) == FALSE) {
if (window_ids_difference(curr_win, stat->new_window) > 4) {
LOG("ERROR - Event on window %#lx\n", extract_window_id(ev));
} else {
LOG("FocusIn event on child of new window - steal focus!\n");
}
stat->should_steal_focus = TRUE;
}
} else {
// Second case: No new window creation process disallow focus in
ret_val = TRUE;
}
} else {
// Event actually on the active window or an inferior window
// of it.
if (stat->encountered_focus_in_event == FALSE) {
// If a focus in event for this window was not yet encountered,
// allow this focus in event and ignore in the future.
stat->encountered_focus_in_event = TRUE;
ret_val = FALSE;
} else {
ret_val = TRUE;
}
}
return ret_val;
}
#ifndef NO_FAKING
// Real functions
void fake_keymap_notify_event(XEvent* outEvent, XEvent* sourceEvent)
{
XEvent ev;
ev.type = KeymapNotify;
ev.xkeymap.serial = sourceEvent->xfocus.serial;
ev.xkeymap.send_event = sourceEvent->xfocus.send_event;
ev.xkeymap.display = sourceEvent->xfocus.display;
ev.xkeymap.window = sourceEvent->xfocus.window;
//bzero(ev.xkeymap.key_vector, 32);
*outEvent = ev;
}
#else
// Dummy functions - faking will not happen.
void fake_keymap_notify_event(XEvent* outEvent, XEvent* sourceEvent)
{
LOG("*** Not faking keymap notify event.\n");
*outEvent = *sourceEvent;
}
static int XSetInputFocus(Display *display, Window focus, int revert_to,
Time time)
{
LOG("*** Not stealing focus.\n");
return 1;
}
#endif
int is_32bit_system()
{
struct utsname sys_info;
int uname_res = uname(&sys_info);
// In case of error, arbitrarily decide it is.
if (uname_res != 0) {
return TRUE;
}
const char arch_64[] = "x86_64";
if (strncmp(sys_info.machine, arch_64, strlen(arch_64)) == 0) {
return FALSE;
}
return TRUE;
}
int is_emulated_32bit()
{
#ifdef __i386__
return !is_32bit_system();
#else
return FALSE;
#endif
}
#define MAX_LIBRARY_PATH (1024)
// Returns the window ID from every type of event
// that should be handled.
Window extract_window_id(XEvent* ev) {
switch (ev->type) {
case FocusIn:
return ev->xfocus.window;
break;
case FocusOut:
return ev->xfocus.window;
break;
case Expose:
return ev->xexpose.window;
break;
case VisibilityNotify:
return ev->xvisibility.window;
break;
case CreateNotify:
return ev->xcreatewindow.window;
break;
case MapNotify:
return ev->xmap.window;
break;
case PropertyNotify:
return ev->xproperty.window;
break;
case DestroyNotify:
return ev->xdestroywindow.window;
break;
case ConfigureNotify:
return ev->xconfigure.window;
break;
case MotionNotify:
return ev->xmotion.window;
break;
case UnmapNotify:
return ev->xunmap.window;
break;
case EnterNotify:
case LeaveNotify:
return ev->xcrossing.window;
break;
case ReparentNotify:
return ev->xreparent.window;
break;
case ClientMessage:
return ev->xclient.window;
break;
case ButtonPress:
case ButtonRelease:
return ev->xbutton.window;
break;
case NoExpose:
break;
default:
LOG("Unknown event type %d\n", ev->type);
};
return 0;
}
int is_library_for_architecture(const char* lib_path, uint16_t arch)
{
Elf32_Ehdr elf32_header;
int elf32_header_size = sizeof(elf32_header);
FILE* lib = fopen(lib_path, "r");
int bytes_read = fread(&elf32_header, 1, elf32_header_size, lib);
fclose(lib);
lib = NULL;
if (bytes_read != elf32_header_size) {
return FALSE;
}
if ((memcmp(elf32_header.e_ident, ELFMAG, sizeof(ELFMAG) - 1) == 0)
&& (elf32_header.e_type == ET_DYN) && (elf32_header.e_machine == arch))
{
return TRUE;
}
return FALSE;
}
int is_usable_library(const char *candidate_library, uint16_t desired_architecture)
{
if (access(candidate_library, F_OK) == 0 &&
is_library_for_architecture(candidate_library, desired_architecture) == TRUE) {
void *ret_handle = dlopen(candidate_library, RTLD_LAZY);
if (ret_handle == NULL) {
return FALSE;
}
dlclose(ret_handle);
return TRUE;
}
return FALSE;
}
int find_xlib_by_arch(const char* possible_locations[],
int locations_length, uint16_t desired_architecture)
{
int i;
for (i = 0; i < locations_length; i++) {
const char* possible_location = possible_locations[i];
if (is_usable_library(possible_location, desired_architecture))
{
return i;
}
}
return -1;
}
int find_xlib_by_env(char *library, uint16_t desired_architecture)
{
char *ld_env = getenv("LD_LIBRARY_PATH");
if (ld_env == 0) {
return FALSE;
}
char *ld_to_parse = strdup(ld_env);
int found_library = FALSE;
char *t = strtok(ld_to_parse, ":");
char potential_library[MAX_LIBRARY_PATH + 1];
while ((t != NULL) && (!found_library)) {
snprintf(potential_library, MAX_LIBRARY_PATH, "%s/libX11.so.6", t);
if (is_usable_library(potential_library, desired_architecture)) {
strcpy(library, potential_library);
found_library = TRUE;
}
t = strtok(NULL, ":");
}
free(ld_to_parse);
return found_library;
}
void* get_xlib_handle()
{
void* ret_handle = NULL;
char library[MAX_LIBRARY_PATH + 1];
const char * possible_locations[] = {
"/usr/lib/libX11.so.6", //default_x11_location
"/usr/lib/x86_64-linux-gnu/libX11.so.6", //debian_x11_location
"/usr/lib/i386-linux-gnu/libX11.so.6", //ubuntu_32bit_x11_location
"/usr/lib64/libX11.so.6", //opensuse_x11_location
"/usr/lib32/libX11.so.6"
};
int locations_len = sizeof(possible_locations) / sizeof(char*);
uint16_t required_lib_arch;
if (is_32bit_system() || is_emulated_32bit()) {
required_lib_arch = EM_386;
} else {
required_lib_arch = EM_X86_64;
}
int suitable_xlib_index = find_xlib_by_arch(possible_locations, locations_len, required_lib_arch);
int found_library = FALSE;
if (suitable_xlib_index >= 0) {
snprintf(library, MAX_LIBRARY_PATH, "%s", possible_locations[suitable_xlib_index]);
found_library = TRUE;
} else {
found_library = find_xlib_by_env(library, required_lib_arch);
}
if (found_library == FALSE) {
const char* desired_arch = (required_lib_arch == EM_386 ? "32-bit" : "64-bit");
fprintf(stderr, "None of the following is a %s version of Xlib:", desired_arch);
int i;
for (i = 0; i < locations_len; i++) {
fprintf(stderr, " %s\n", possible_locations[i]);
}
return NULL;
}
ret_handle = dlopen(library, RTLD_LAZY);
if (ret_handle == NULL) {
fprintf(stderr, "Failed to dlopen %s\n", library);
fprintf(stderr, "dlerror says: %s\n", dlerror());
}
return ret_handle;
}
void print_event_to_log(Display* dpy, XEvent* ev)
{
#ifdef DEBUG_PRINTOUTS
if ((ev->type != PropertyNotify) && (ev->type != ConfigureNotify)) {
print_event(g_out_stream, ev, dpy);
}
#endif
}
// This global variable is intentionally declared here - as I wish the rest
// of the functions will act on it as a parameter.
FocusKeepStatus g_focus_status;
void initFocusStatusAndXQueryTree() {
if (g_library_inited == FALSE) {
LOG("Library initialized.\n");
g_library_inited = TRUE;
init_cached_xquerytree();
init_focus_keep_struct(&g_focus_status);
}
}
int XNextEvent(Display *display, XEvent *outEvent) {
// Code to pull the real function handle from X11 library.
void *handle = NULL;
//This will turn the function proto into a function pointer declaration
int (*real_func)(Display *display, XEvent *outEvent) = NULL;
handle = get_xlib_handle();
if (handle == NULL) {
return -1;
}
// The real event from XNextEvent
XEvent realEvent;
// Find the real function.
real_func = dlsym(handle, "XNextEvent");
// Invoke the real function.
int rf_ret = real_func(display, &realEvent);
OPEN_LOGGING_FILE;
initFocusStatusAndXQueryTree();
// This display object will be used to inquire X server
// about inferior and parent windows.
Display* dpy = display;
//assert(dpy != NULL);
print_event_to_log(dpy, &realEvent);
// Is the event on a window other than the active one?
// If so, update gActiveWindow on two cases:
// 1. It's the first window known to the module.
// 2. It's the second window known to the module. The second
// window is the actual browser window (the first one is just a
// set-up one).
//
if ((get_active_window(&g_focus_status) == 0) && (is_focus_in(&realEvent))) {
set_active_window(&g_focus_status, &realEvent);
} else {
identify_switch_situation(&g_focus_status);
}
if (is_reparent_notify(&realEvent)) {
identify_new_window_situation(&g_focus_status, &realEvent);
}
if (is_destroy_notify(&realEvent)) {
identify_active_destroyed(&g_focus_status, &realEvent);
}
if ((g_focus_status.during_switch == TRUE) ||
(get_active_window(&g_focus_status) == 0)) {
LOG("During switch: %d Active win: %#lx during close: %d\n",
g_focus_status.during_switch, get_active_window(&g_focus_status),
g_focus_status.during_close);
*outEvent = realEvent;
} else if (should_discard_focus_out_event(&g_focus_status, dpy, &realEvent)) {
// Fake an event!
fake_keymap_notify_event(outEvent, &realEvent);
LOG("Fake event for focus out.\n");
} else if (should_discard_focus_in_event(&g_focus_status, dpy, &realEvent)) {
fake_keymap_notify_event(outEvent, &realEvent);
LOG("Fake event for focus in.\n");
} else {
*outEvent = realEvent;
}
steal_focus_back_if_needed(&g_focus_status, dpy);
dlclose(handle);
CLOSE_LOGGING_FILE;
return rf_ret;
}
void notify_of_switch_to_window(long window_id) {
initFocusStatusAndXQueryTree();
g_focus_status.start_switch_window = TRUE;
OPEN_LOGGING_FILE;
LOG("Notify of switch-to-window with id %d\n", window_id);
CLOSE_LOGGING_FILE;
}
void notify_of_close_window(long window_id) {
initFocusStatusAndXQueryTree();
g_focus_status.start_close_window = TRUE;
OPEN_LOGGING_FILE;
if (0 == window_id) {
LOG("Notify of close-all-windows.\n");
} else {
LOG("Notify of close-window with id %n", window_id);
}
CLOSE_LOGGING_FILE;
}