Path: blob/trunk/cpp/linux-specific/x_ignore_nofocus.c
2867 views
#include <stdio.h>1#include <X11/Xlib.h>2#include <X11/X.h>3#include <dlfcn.h>4#include <sys/utsname.h>5#include <string.h>6#include "print_events.h"7#include <time.h>8#include <sys/time.h>9#include <stdlib.h>10#include <assert.h>11#include <unistd.h>12#include <elf.h>1314#ifndef TRUE15#define TRUE 116#endif1718#ifndef FALSE19#define FALSE 020#endif2122// Define this to prevent events from being faked.23//#undef NO_FAKING2425//#define DEBUG_PRINTOUTS2627#ifdef DEBUG_PRINTOUTS28FILE* g_out_stream = 0;29#define LOG(...) if (g_out_stream != NULL) { fprintf(g_out_stream, __VA_ARGS__); fflush(g_out_stream); }30#define OPEN_LOGGING_FILE { g_out_stream = fopen("/tmp/x_ignore_focus_log.txt", "a+"); }31#define CLOSE_LOGGING_FILE { fclose(g_out_stream); g_out_stream = NULL; }32#else33// This is to prevent compiler warning for unused variables.34void do_nothing(const char* fmt, ...) {}35#define LOG(...) do_nothing(__VA_ARGS__)36#define OPEN_LOGGING_FILE ;37#define CLOSE_LOGGING_FILE ;38#endif3940int g_library_inited = FALSE;4142struct _FocusKeepStatus {43Window active_window;44Window new_window;45int start_switch_window;46int start_close_window;47int during_switch;48int during_close;49int should_steal_focus;50int encountered_focus_in_event;51int active_window_from_close;52};5354typedef struct _FocusKeepStatus FocusKeepStatus;5556void init_focus_keep_struct(FocusKeepStatus* stat)57{58stat->active_window = 0;59stat->new_window = 0;60stat->start_switch_window = FALSE;61stat->start_close_window = FALSE;62stat->during_switch = FALSE;63stat->during_close = FALSE;64stat->should_steal_focus = FALSE;65// This boolean is for remembering if we already had a FocusIn event and66// never re-send that event as well, not to break clients which expect to get67// FocusOut before FocusIn68stat->encountered_focus_in_event = FALSE;69// This remembers if the active was learnt due to a close70stat->active_window_from_close = FALSE;71};7273Window get_active_window(FocusKeepStatus* stat)74{75return stat->active_window;76}7778int is_focus_out(XEvent* ev)79{80return (ev->type == FocusOut);81}8283int is_focus_in(XEvent* ev)84{85return (ev->type == FocusIn);86}8788int is_reparent_notify(XEvent* ev)89{90return (ev->type == ReparentNotify);91}9293int is_destroy_notify(XEvent* ev)94{95return (ev->type == DestroyNotify);96}9798Window extract_window_id(XEvent* ev);99100struct {101Window window_id;102Window* related_windows;103} g_cached_xquerytree;104105void init_cached_xquerytree()106{107g_cached_xquerytree.window_id = 0;108g_cached_xquerytree.related_windows = 0;109}110111// Performing XQueryTree after UnmapNotify for some of the112// windows will cause a crash. Cache to prevent it.113int cache_xquery_result(Display* dpy, Window for_win) {114Window root_win = 0;115Window parent_win = 0;116Window* childs_list = NULL;117unsigned int num_childs = 0;118int k = 0;119120if ((g_cached_xquerytree.window_id == for_win) &&121(g_cached_xquerytree.related_windows != NULL)) {122return TRUE;123}124125LOG("Invoking XQueryTree for window %#lx\n", for_win);126int queryRes = XQueryTree(dpy, for_win, &root_win,127&parent_win, &childs_list, &num_childs);128if (queryRes == 0) {129LOG("XQueryTree failed, rc=%d\n", queryRes);130return FALSE;131}132133if (g_cached_xquerytree.related_windows != NULL) {134free(g_cached_xquerytree.related_windows);135g_cached_xquerytree.related_windows = NULL;136}137138int numRelatedWindows = (1 /* parent_win */ +1391 /* actual win */ + num_childs + 1 /* NULL */);140141142g_cached_xquerytree.related_windows = malloc(sizeof(Window) * numRelatedWindows);143LOG("Allocated at address %p , numRelWindows: %d\n",144g_cached_xquerytree.related_windows, numRelatedWindows);145int relatedWinsIndex = 0;146g_cached_xquerytree.related_windows[relatedWinsIndex++] = parent_win;147g_cached_xquerytree.related_windows[relatedWinsIndex++] = for_win;148149if ((num_childs > 0) && (childs_list != NULL)) {150for (k = 0; k < num_childs; k++) {151g_cached_xquerytree.related_windows[relatedWinsIndex++] = childs_list[k];152}153XFree(childs_list);154childs_list = NULL;155}156g_cached_xquerytree.related_windows[relatedWinsIndex] = 0;157158g_cached_xquerytree.window_id = for_win;159160return TRUE;161}162163int lookup_in_xquery_cache(Window ev_win)164{165int ret_val = FALSE;166int k = 0;167if (g_cached_xquerytree.related_windows == NULL) {168LOG("related_windows is NULL, cache is inconsistent.\n");169return FALSE;170}171while ((g_cached_xquerytree.related_windows[k] != 0) && (!ret_val)) {172if (g_cached_xquerytree.related_windows[k] == ev_win) {173ret_val = TRUE;174}175k++;176}177178return ret_val;179}180181int window_ids_difference(Window win_one, Window win_two)182{183return (abs(win_one - win_two));184}185186int event_on_active_or_adj_window(Display* dpy, XEvent* ev, Window active_win)187{188Window ev_win;189int ret_val = FALSE;190191ev_win = extract_window_id(ev);192193// This is probably also essential as on focus in events on new windows194// XQueryTree should not be called yet.195if (active_win == ev_win) {196return TRUE;197}198199// "Obviously" related windows - ID of active window200// and event_window differ by 1. By performing this check first,201// we avoid calling XQueryTree which causes a segfault202// if the window queried is being closed.203if (abs(active_win - ev_win) <= 1) {204ret_val = TRUE;205} else {206if (cache_xquery_result(dpy, active_win)) {207ret_val = lookup_in_xquery_cache(ev_win);208}209}210211return ret_val;212}213214#define MAX_BUFFER_SIZE (256)215216void identify_switch_situation(FocusKeepStatus* stat)217{218if (stat->start_switch_window || stat->start_close_window) {219// In the middle of a window switch.220Window old_active = get_active_window(stat);221stat->active_window = 0;222stat->during_switch = TRUE;223224if (stat->start_close_window) {225stat->during_close = TRUE;226}227228LOG("Window switching detected, active was: %#lx close: %d\n",229old_active, stat->during_close);230231// Reset the the flags.232stat->start_switch_window = FALSE;233stat->start_close_window = FALSE;234}235}236237void set_active_window(FocusKeepStatus* stat, XEvent* ev)238{239stat->active_window = extract_window_id(ev);240if (stat->during_close) {241stat->active_window_from_close = TRUE;242} else {243stat->active_window_from_close = FALSE;244}245stat->encountered_focus_in_event = FALSE;246stat->during_switch = FALSE;247stat->start_switch_window = FALSE;248stat->start_close_window = FALSE;249LOG("Setting Active Window due to FocusIn: %#lx (from close: %d)\n",250get_active_window(stat), stat->active_window_from_close);251}252253void identify_new_window_situation(FocusKeepStatus* stat, XEvent* ev)254{255Window new_win = extract_window_id(ev);256assert(is_reparent_notify(ev));257258if (get_active_window(stat) != 0) {259stat->new_window = new_win;260LOG("New window being created: %#lx\n", stat->new_window);261} else {262LOG("Reparent notify for window: %#lx, but no active.\n", new_win);263}264}265266void identify_active_destroyed(FocusKeepStatus* stat, XEvent* ev)267{268assert(is_destroy_notify(ev));269270if (extract_window_id(ev) == get_active_window(stat)) {271LOG("Active window: %#lx is destroyed!\n", get_active_window(stat));272stat->active_window = 0;273}274}275276void steal_focus_back_if_needed(FocusKeepStatus* stat, Display* dpy)277{278if ((stat->should_steal_focus) && (get_active_window(stat) != 0)) {279stat->should_steal_focus = FALSE;280281if ((!stat->during_close) || (stat->active_window_from_close)) {282LOG("Stealing focus back to %#lx\n", get_active_window(stat));283stat->new_window = 0;284285XSetInputFocus(dpy, get_active_window(stat), RevertToParent, CurrentTime);286// Allow a focus in event to flow again to the window considered287// active.288stat->encountered_focus_in_event = FALSE;289} else {290LOG("Not stealing focus back. During close: %d Active from close: %d.\n",291stat->during_close, stat->active_window_from_close);292// Set during_close to false here - This is the point where the state293// transition is done - specifically, we consider the entire close294// process to be completed.295stat->during_close = FALSE;296}297}298}299300int should_discard_focus_out_event(FocusKeepStatus* stat, Display* dpy,301XEvent *ev)302{303int ret_val = FALSE;304if (is_focus_out(ev) == FALSE) {305return FALSE;306}307308const int detail = ev->xfocus.detail;309310if (stat->new_window != 0) {311/*312if (!(event_on_active_or_adj_window(dpy, ev, stat->new_window)313|| event_on_active_or_adj_window(dpy, ev, get_active_window(stat)))) {314LOG( "ERROR - Event on window %#lx, which is neither new nor active.\n",315extract_window_id(ev));316} else */ {317LOG("Event on new/active (%#lx) during new window creation, allowing.",318extract_window_id(ev));319LOG(" New: %#lx Active: %#lx\n", stat->new_window, stat->active_window);320}321return FALSE;322}323324if (event_on_active_or_adj_window(dpy, ev, get_active_window(stat))) {325// If moving ownership between sub-windows of the same Firefox window.326if ((detail == NotifyAncestor) || (detail == NotifyInferior)) {327// Allow this one.328LOG("Focus will move to ancestor / inferior (%d). Allowing.\n", detail);329stat->encountered_focus_in_event = FALSE;330} else {331// Disallow transfer of focus to outside windows.332if (!stat->active_window_from_close) {333ret_val = TRUE;334} else {335LOG("FocusOut event, but active window from close. Not discarding.\n");336}337}338} else {339LOG("Got Focus out event on window %#lx but active window is %#lx\n",340extract_window_id(ev), get_active_window(stat));341}342343return ret_val;344}345346int should_discard_focus_in_event(FocusKeepStatus* stat, Display* dpy,347XEvent *ev)348{349int ret_val = FALSE;350if (is_focus_in(ev) == FALSE) {351return FALSE;352}353354// Event not on active window - It's either on a new window currently being355// created or on a different firefox one. On the first case, it will356// be allowed through, but blocked on the second case.357if (!event_on_active_or_adj_window(dpy, ev, get_active_window(stat))) {358LOG("Got Focus in event on window %#lx but active window is %#lx\n",359extract_window_id(ev), get_active_window(stat));360361if (stat->new_window != 0) {362// If we are in the process of a new window creation, do not ignore363// this focus in event - allow it both for the new window364// and for a child window of it. However, if this is a focus in365// event for a child window (not the new window itself), then366// steal focus back from it afterwards.367ret_val = FALSE;368Window curr_win = extract_window_id(ev);369if (curr_win == stat->new_window) {370LOG("FocusIn event on new window - allowing.\n");371} else {372//if (event_on_active_or_adj_window(dpy, ev, stat->new_window) == FALSE) {373if (window_ids_difference(curr_win, stat->new_window) > 4) {374LOG("ERROR - Event on window %#lx\n", extract_window_id(ev));375} else {376LOG("FocusIn event on child of new window - steal focus!\n");377}378stat->should_steal_focus = TRUE;379}380} else {381// Second case: No new window creation process disallow focus in382ret_val = TRUE;383}384} else {385// Event actually on the active window or an inferior window386// of it.387if (stat->encountered_focus_in_event == FALSE) {388// If a focus in event for this window was not yet encountered,389// allow this focus in event and ignore in the future.390stat->encountered_focus_in_event = TRUE;391ret_val = FALSE;392} else {393ret_val = TRUE;394}395}396397return ret_val;398}399400#ifndef NO_FAKING401// Real functions402void fake_keymap_notify_event(XEvent* outEvent, XEvent* sourceEvent)403{404XEvent ev;405ev.type = KeymapNotify;406ev.xkeymap.serial = sourceEvent->xfocus.serial;407ev.xkeymap.send_event = sourceEvent->xfocus.send_event;408ev.xkeymap.display = sourceEvent->xfocus.display;409ev.xkeymap.window = sourceEvent->xfocus.window;410//bzero(ev.xkeymap.key_vector, 32);411*outEvent = ev;412}413414#else415// Dummy functions - faking will not happen.416void fake_keymap_notify_event(XEvent* outEvent, XEvent* sourceEvent)417{418LOG("*** Not faking keymap notify event.\n");419*outEvent = *sourceEvent;420}421422static int XSetInputFocus(Display *display, Window focus, int revert_to,423Time time)424{425LOG("*** Not stealing focus.\n");426return 1;427}428429#endif430431int is_32bit_system()432{433struct utsname sys_info;434int uname_res = uname(&sys_info);435// In case of error, arbitrarily decide it is.436if (uname_res != 0) {437return TRUE;438}439440const char arch_64[] = "x86_64";441if (strncmp(sys_info.machine, arch_64, strlen(arch_64)) == 0) {442return FALSE;443}444445return TRUE;446}447448int is_emulated_32bit()449{450#ifdef __i386__451return !is_32bit_system();452#else453return FALSE;454#endif455}456457#define MAX_LIBRARY_PATH (1024)458459// Returns the window ID from every type of event460// that should be handled.461Window extract_window_id(XEvent* ev) {462switch (ev->type) {463case FocusIn:464return ev->xfocus.window;465break;466case FocusOut:467return ev->xfocus.window;468break;469case Expose:470return ev->xexpose.window;471break;472case VisibilityNotify:473return ev->xvisibility.window;474break;475case CreateNotify:476return ev->xcreatewindow.window;477break;478case MapNotify:479return ev->xmap.window;480break;481case PropertyNotify:482return ev->xproperty.window;483break;484case DestroyNotify:485return ev->xdestroywindow.window;486break;487case ConfigureNotify:488return ev->xconfigure.window;489break;490case MotionNotify:491return ev->xmotion.window;492break;493case UnmapNotify:494return ev->xunmap.window;495break;496case EnterNotify:497case LeaveNotify:498return ev->xcrossing.window;499break;500case ReparentNotify:501return ev->xreparent.window;502break;503case ClientMessage:504return ev->xclient.window;505break;506case ButtonPress:507case ButtonRelease:508return ev->xbutton.window;509break;510case NoExpose:511break;512default:513LOG("Unknown event type %d\n", ev->type);514};515return 0;516}517518int is_library_for_architecture(const char* lib_path, uint16_t arch)519{520Elf32_Ehdr elf32_header;521int elf32_header_size = sizeof(elf32_header);522523FILE* lib = fopen(lib_path, "r");524int bytes_read = fread(&elf32_header, 1, elf32_header_size, lib);525fclose(lib);526lib = NULL;527528if (bytes_read != elf32_header_size) {529return FALSE;530}531532if ((memcmp(elf32_header.e_ident, ELFMAG, sizeof(ELFMAG) - 1) == 0)533&& (elf32_header.e_type == ET_DYN) && (elf32_header.e_machine == arch))534{535return TRUE;536}537538return FALSE;539}540541int is_usable_library(const char *candidate_library, uint16_t desired_architecture)542{543if (access(candidate_library, F_OK) == 0 &&544is_library_for_architecture(candidate_library, desired_architecture) == TRUE) {545void *ret_handle = dlopen(candidate_library, RTLD_LAZY);546if (ret_handle == NULL) {547return FALSE;548}549dlclose(ret_handle);550551return TRUE;552}553return FALSE;554}555556int find_xlib_by_arch(const char* possible_locations[],557int locations_length, uint16_t desired_architecture)558{559int i;560for (i = 0; i < locations_length; i++) {561const char* possible_location = possible_locations[i];562563if (is_usable_library(possible_location, desired_architecture))564{565return i;566}567}568569return -1;570}571572573int find_xlib_by_env(char *library, uint16_t desired_architecture)574{575char *ld_env = getenv("LD_LIBRARY_PATH");576if (ld_env == 0) {577return FALSE;578}579580char *ld_to_parse = strdup(ld_env);581582int found_library = FALSE;583char *t = strtok(ld_to_parse, ":");584char potential_library[MAX_LIBRARY_PATH + 1];585586while ((t != NULL) && (!found_library)) {587snprintf(potential_library, MAX_LIBRARY_PATH, "%s/libX11.so.6", t);588if (is_usable_library(potential_library, desired_architecture)) {589strcpy(library, potential_library);590found_library = TRUE;591}592t = strtok(NULL, ":");593}594595free(ld_to_parse);596return found_library;597}598599void* get_xlib_handle()600{601void* ret_handle = NULL;602char library[MAX_LIBRARY_PATH + 1];603604const char * possible_locations[] = {605"/usr/lib/libX11.so.6", //default_x11_location606"/usr/lib/x86_64-linux-gnu/libX11.so.6", //debian_x11_location607"/usr/lib/i386-linux-gnu/libX11.so.6", //ubuntu_32bit_x11_location608"/usr/lib64/libX11.so.6", //opensuse_x11_location609"/usr/lib32/libX11.so.6"610};611int locations_len = sizeof(possible_locations) / sizeof(char*);612613uint16_t required_lib_arch;614if (is_32bit_system() || is_emulated_32bit()) {615required_lib_arch = EM_386;616} else {617required_lib_arch = EM_X86_64;618}619int suitable_xlib_index = find_xlib_by_arch(possible_locations, locations_len, required_lib_arch);620int found_library = FALSE;621if (suitable_xlib_index >= 0) {622snprintf(library, MAX_LIBRARY_PATH, "%s", possible_locations[suitable_xlib_index]);623found_library = TRUE;624} else {625found_library = find_xlib_by_env(library, required_lib_arch);626}627if (found_library == FALSE) {628const char* desired_arch = (required_lib_arch == EM_386 ? "32-bit" : "64-bit");629fprintf(stderr, "None of the following is a %s version of Xlib:", desired_arch);630int i;631for (i = 0; i < locations_len; i++) {632fprintf(stderr, " %s\n", possible_locations[i]);633}634return NULL;635}636637ret_handle = dlopen(library, RTLD_LAZY);638if (ret_handle == NULL) {639fprintf(stderr, "Failed to dlopen %s\n", library);640fprintf(stderr, "dlerror says: %s\n", dlerror());641}642643return ret_handle;644}645646void print_event_to_log(Display* dpy, XEvent* ev)647{648#ifdef DEBUG_PRINTOUTS649if ((ev->type != PropertyNotify) && (ev->type != ConfigureNotify)) {650print_event(g_out_stream, ev, dpy);651}652#endif653}654655// This global variable is intentionally declared here - as I wish the rest656// of the functions will act on it as a parameter.657FocusKeepStatus g_focus_status;658659void initFocusStatusAndXQueryTree() {660if (g_library_inited == FALSE) {661LOG("Library initialized.\n");662g_library_inited = TRUE;663init_cached_xquerytree();664init_focus_keep_struct(&g_focus_status);665}666}667668int XNextEvent(Display *display, XEvent *outEvent) {669// Code to pull the real function handle from X11 library.670void *handle = NULL;671672//This will turn the function proto into a function pointer declaration673int (*real_func)(Display *display, XEvent *outEvent) = NULL;674handle = get_xlib_handle();675676if (handle == NULL) {677return -1;678}679680// The real event from XNextEvent681XEvent realEvent;682683// Find the real function.684real_func = dlsym(handle, "XNextEvent");685// Invoke the real function.686int rf_ret = real_func(display, &realEvent);687688OPEN_LOGGING_FILE;689690initFocusStatusAndXQueryTree();691692// This display object will be used to inquire X server693// about inferior and parent windows.694Display* dpy = display;695//assert(dpy != NULL);696697print_event_to_log(dpy, &realEvent);698699// Is the event on a window other than the active one?700// If so, update gActiveWindow on two cases:701// 1. It's the first window known to the module.702// 2. It's the second window known to the module. The second703// window is the actual browser window (the first one is just a704// set-up one).705//706if ((get_active_window(&g_focus_status) == 0) && (is_focus_in(&realEvent))) {707set_active_window(&g_focus_status, &realEvent);708} else {709identify_switch_situation(&g_focus_status);710}711712if (is_reparent_notify(&realEvent)) {713identify_new_window_situation(&g_focus_status, &realEvent);714}715716if (is_destroy_notify(&realEvent)) {717identify_active_destroyed(&g_focus_status, &realEvent);718}719720if ((g_focus_status.during_switch == TRUE) ||721(get_active_window(&g_focus_status) == 0)) {722LOG("During switch: %d Active win: %#lx during close: %d\n",723g_focus_status.during_switch, get_active_window(&g_focus_status),724g_focus_status.during_close);725*outEvent = realEvent;726} else if (should_discard_focus_out_event(&g_focus_status, dpy, &realEvent)) {727// Fake an event!728fake_keymap_notify_event(outEvent, &realEvent);729LOG("Fake event for focus out.\n");730} else if (should_discard_focus_in_event(&g_focus_status, dpy, &realEvent)) {731fake_keymap_notify_event(outEvent, &realEvent);732LOG("Fake event for focus in.\n");733} else {734*outEvent = realEvent;735}736737steal_focus_back_if_needed(&g_focus_status, dpy);738739dlclose(handle);740CLOSE_LOGGING_FILE;741return rf_ret;742}743744void notify_of_switch_to_window(long window_id) {745initFocusStatusAndXQueryTree();746g_focus_status.start_switch_window = TRUE;747OPEN_LOGGING_FILE;748LOG("Notify of switch-to-window with id %d\n", window_id);749CLOSE_LOGGING_FILE;750}751752void notify_of_close_window(long window_id) {753initFocusStatusAndXQueryTree();754g_focus_status.start_close_window = TRUE;755OPEN_LOGGING_FILE;756if (0 == window_id) {757LOG("Notify of close-all-windows.\n");758} else {759LOG("Notify of close-window with id %n", window_id);760}761CLOSE_LOGGING_FILE;762}763764765