From 39960171329350de2956286ffc285ebe335e857e Mon Sep 17 00:00:00 2001 From: Thomas Jaeger Date: Sun, 23 Sep 2012 15:06:27 -0400 Subject: [PATCH] Refactor event handling code --- actiondb.h | 2 + actions.cc | 2 +- grabber.cc | 34 +- grabber.h | 15 +- handler.cc | 1132 +++++++++++++++++++++++++++++++++++++++++++++++++ handler.h | 104 +++++ main.cc | 1183 ++-------------------------------------------------- prefs.cc | 2 +- 8 files changed, 1288 insertions(+), 1186 deletions(-) create mode 100644 handler.cc create mode 100644 handler.h diff --git a/actiondb.h b/actiondb.h index 35a548aa..1189b10c 100644 --- a/actiondb.h +++ b/actiondb.h @@ -51,6 +51,8 @@ class Unique; class Modifiers; typedef boost::shared_ptr RModifiers; +bool mods_equal(RModifiers m1, RModifiers m2); + class Action { friend class boost::serialization::access; friend std::ostream& operator<<(std::ostream& output, const Action& c); diff --git a/actions.cc b/actions.cc index bc4145b9..222f5b85 100644 --- a/actions.cc +++ b/actions.cc @@ -602,7 +602,7 @@ bool Actions::select_app(const Gtk::TreeModel::Path& path, const Gtk::TreeModel: } void Actions::on_add_app() { - std::string name = select_window(); + std::string name = grabber->select_window(); if (actions.apps.count(name)) { apps_model->foreach(sigc::bind(sigc::mem_fun(*this, &Actions::select_app), actions.apps[name])); return; diff --git a/grabber.cc b/grabber.cc index da378d7c..1d65df50 100644 --- a/grabber.cc +++ b/grabber.cc @@ -13,7 +13,7 @@ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "actiondb.h" // TODO +#include "handler.h" #include "grabber.h" #include "main.h" #include "prefs.h" @@ -23,7 +23,6 @@ #include #include -extern Window get_window(Window w, Atom prop); extern Source disabled; extern Source current_app_window; extern Source recording; @@ -91,7 +90,7 @@ BiMap minimized; unsigned int minimized_n = 0; void get_frame(Window w) { - Window frame = get_window(w, *_NET_FRAME_WINDOW); + Window frame = xstate->get_window(w, *_NET_FRAME_WINDOW); if (!frame) return; frame_win.add(frame, w); @@ -143,7 +142,7 @@ bool Children::handle(XEvent &ev) { minimized.erase2(ev.xproperty.window); return true; } - if (has_atom(ev.xproperty.window, *_NET_WM_STATE, *_NET_WM_STATE_HIDDEN)) + if (xstate->has_atom(ev.xproperty.window, *_NET_WM_STATE, *_NET_WM_STATE_HIDDEN)) minimized.add(minimized_n++, ev.xproperty.window); else minimized.erase2(ev.xproperty.window); @@ -204,7 +203,7 @@ class IdleNotifier : public Base { void run() { f(); } public: IdleNotifier(sigc::slot f_) : f(f_) {} - virtual void notify() { queue(sigc::mem_fun(*this, &IdleNotifier::run)); } + virtual void notify() { xstate->queue(sigc::mem_fun(*this, &IdleNotifier::run)); } }; void Grabber::unminimize() { @@ -262,8 +261,6 @@ bool Grabber::init_xi() { return false; } - current_dev = NULL; - for (int i = 0; i < n; i++) new_device(info + i); XIFreeDeviceInfo(info); @@ -321,9 +318,8 @@ bool Grabber::hierarchy_changed(XIHierarchyEvent *event) { } else if (info->flags & XISlaveRemoved) { if (verbosity >= 1) printf("Device %d removed.\n", info->deviceid); + xstate->remove_device(info->deviceid); xi_devs.erase(info->deviceid); - if (current_dev && current_dev->dev == info->deviceid) - current_dev = NULL; changed = true; } else if (info->flags & (XISlaveAttached | XISlaveDetached)) { DeviceMap::iterator i = xi_devs.find(info->deviceid); @@ -419,8 +415,7 @@ void Grabber::XiDevice::grab_button(ButtonInfo &bi, bool grab) { XIGrabButton(dpy, dev, bi.button, ROOT, None, GrabModeAsync, GrabModeAsync, False, &device_mask, nmods, modifiers); else { XIUngrabButton(dpy, dev, bi.button, ROOT, nmods, modifiers); - if (current_dev && current_dev->dev == dev) - xinput_pressed.clear(); + xstate->ungrab(dev); } } @@ -437,8 +432,7 @@ void Grabber::grab_xi(bool grab) { void Grabber::XiDevice::grab_device(GrabState grab) { if (grab == GrabNo) { XIUngrabDevice(dpy, dev, CurrentTime); - if (current_dev && current_dev->dev == dev) - xinput_pressed.clear(); + xstate->ungrab(dev); return; } XIGrabDevice(dpy, dev, ROOT, CurrentTime, None, GrabModeAsync, GrabModeAsync, False, @@ -482,6 +476,18 @@ void Grabber::set() { } } +void Grabber::queue_suspend() { + xstate->queue(sigc::mem_fun(*this, &Grabber::suspend)); +} + +void Grabber::queue_resume() { + xstate->queue(sigc::mem_fun(*this, &Grabber::resume)); +} + +std::string Grabber::select_window() { + return xstate->select_window(); +} + bool Grabber::is_grabbed(guint b) { for (std::vector::iterator i = buttons.begin(); i != buttons.end(); i++) if (i->button == b) @@ -543,7 +549,7 @@ void Grabber::update() { } // Fuck Xlib -bool has_wm_state(Window w) { +static bool has_wm_state(Window w) { static XAtom WM_STATE("WM_STATE"); Atom actual_type_return; int actual_format_return; diff --git a/grabber.h b/grabber.h index c1878784..9b843af4 100644 --- a/grabber.h +++ b/grabber.h @@ -46,13 +46,6 @@ class Children { class Grabber; extern Grabber *grabber; -bool has_wm_state(Window w); -bool has_atom(Window w, Atom prop, Atom value); - -void queue(sigc::slot f); -std::string select_window(); -void fake_core_button(guint b, bool press); - class Grabber { friend class Handler; friend class StrokeHandler; @@ -110,8 +103,9 @@ class Grabber { bool handle(XEvent &ev) { return children.handle(ev); } Out *current_class; - void queue_suspend() { queue(sigc::mem_fun(*this, &Grabber::suspend)); } - void queue_resume() { queue(sigc::mem_fun(*this, &Grabber::resume)); } + void queue_suspend(); + void queue_resume(); + std::string select_window(); void new_device(XIDeviceInfo *); @@ -126,9 +120,6 @@ class Grabber { void unminimize(); }; -extern Grabber::XiDevice *current_dev; -extern std::set xinput_pressed; - class GrabFailedException : public std::exception { char *msg; public: diff --git a/handler.cc b/handler.cc new file mode 100644 index 00000000..a41ffd7a --- /dev/null +++ b/handler.cc @@ -0,0 +1,1132 @@ +/* + * Copyright (c) 2008-2012, Thomas Jaeger + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include "handler.h" +#include "main.h" +#include "trace.h" +#include "win.h" // Why? +#include "prefs.h" // Why? +#include +#include +#include +//#include +#include +#include +// From #include +// which is not C++-safe +#define X_GrabDeviceButton 17 + +XState *xstate = NULL; + +extern Window get_app_window(Window w); +extern Source current_app_window; +extern boost::shared_ptr trace; + + +boost::shared_ptr > stroke_action; + +static XAtom EASYSTROKE_PING("EASYSTROKE_PING"); + +bool XState::idle() { + return !handler->child; +} + +void XState::queue(sigc::slot f) { + if (idle()) { + f(); + XFlush(dpy); + } else + queued.push_back(f); +} + +void XState::handle_enter_leave(XEvent &ev) { + if (ev.xcrossing.mode == NotifyGrab) + return; + if (ev.xcrossing.detail == NotifyInferior) + return; + Window w = ev.xcrossing.window; + if (ev.type == EnterNotify) { + current_app_window.set(get_app_window(w)); + if (verbosity >= 3) + printf("Entered window 0x%lx -> 0x%lx\n", w, current_app_window.get()); + } else printf("Error: Bogus Enter/Leave event\n"); +} + +#define H (handler->top()) +void XState::handle_event(XEvent &ev) { + + switch(ev.type) { + case EnterNotify: + case LeaveNotify: + handle_enter_leave(ev); + return; + + case PropertyNotify: + if (current_app_window.get() == ev.xproperty.window && ev.xproperty.atom == XA_WM_CLASS) + current_app_window.notify(); + return; + + case ButtonPress: + if (verbosity >= 3) + printf("Press (master): %d (%d, %d) at t = %ld\n", ev.xbutton.button, ev.xbutton.x, ev.xbutton.y, ev.xbutton.time); + H->press_master(ev.xbutton.button, ev.xbutton.time); + return; + + case ClientMessage: + if (ev.xclient.window != ping_window) + return; + if (ev.xclient.message_type == *EASYSTROKE_PING) { + if (verbosity >= 3) + printf("Pong\n"); + H->pong(); + } + return; + + case MappingNotify: + if (ev.xmapping.request == MappingPointer) + update_core_mapping(); + if (ev.xmapping.request == MappingKeyboard || ev.xmapping.request == MappingModifier) + XRefreshKeyboardMapping(&ev.xmapping); + return; + case GenericEvent: + if (ev.xcookie.extension == grabber->opcode && XGetEventData(dpy, &ev.xcookie)) { + handle_xi2_event((XIDeviceEvent *)ev.xcookie.data); + XFreeEventData(dpy, &ev.xcookie); + } + } +} + +void XState::activate_window(Window w, Time t) { + static XAtom _NET_ACTIVE_WINDOW("_NET_ACTIVE_WINDOW"); + static XAtom _NET_WM_WINDOW_TYPE("_NET_WM_WINDOW_TYPE"); + static XAtom _NET_WM_WINDOW_TYPE_DOCK("_NET_WM_WINDOW_TYPE_DOCK"); + static XAtom WM_PROTOCOLS("WM_PROTOCOLS"); + static XAtom WM_TAKE_FOCUS("WM_TAKE_FOCUS"); + + if (w == get_window(ROOT, *_NET_ACTIVE_WINDOW)) + return; + + Atom window_type = get_atom(w, *_NET_WM_WINDOW_TYPE); + if (window_type == *_NET_WM_WINDOW_TYPE_DOCK) + return; + + XWMHints *wm_hints = XGetWMHints(dpy, w); + if (wm_hints) { + bool input = wm_hints->input; + XFree(wm_hints); + if (!input) + return; + } + + if (!has_atom(w, *WM_PROTOCOLS, *WM_TAKE_FOCUS)) + return; + + XWindowAttributes attr; + if (XGetWindowAttributes(dpy, w, &attr) && attr.override_redirect) + return; + + if (verbosity >= 3) + printf("Giving focus to window 0x%lx\n", w); + + icccm_client_message(w, *WM_TAKE_FOCUS, t); +} + +Window XState::get_window(Window w, Atom prop) { + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *prop_return = NULL; + + if (XGetWindowProperty(dpy, w, prop, 0, sizeof(Atom), False, XA_WINDOW, &actual_type, &actual_format, + &nitems, &bytes_after, &prop_return) != Success) + return None; + if (!prop_return) + return None; + Window ret = *(Window *)prop_return; + XFree(prop_return); + return ret; +} + +Atom XState::get_atom(Window w, Atom prop) { + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *prop_return = NULL; + + if (XGetWindowProperty(dpy, w, prop, 0, sizeof(Atom), False, XA_ATOM, &actual_type, &actual_format, + &nitems, &bytes_after, &prop_return) != Success) + return None; + if (!prop_return) + return None; + Atom atom = *(Atom *)prop_return; + XFree(prop_return); + return atom; +} + +bool XState::has_atom(Window w, Atom prop, Atom value) { + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *prop_return = NULL; + + if (XGetWindowProperty(dpy, w, prop, 0, sizeof(Atom), False, XA_ATOM, &actual_type, &actual_format, + &nitems, &bytes_after, &prop_return) != Success) + return None; + if (!prop_return) + return None; + Atom *atoms = (Atom *)prop_return; + bool ans = false; + for (unsigned long i = 0; i < nitems; i++) + if (atoms[i] == value) + ans = true; + XFree(prop_return); + return ans; +} + +void XState::icccm_client_message(Window w, Atom a, Time t) { + static XAtom WM_PROTOCOLS("WM_PROTOCOLS"); + XClientMessageEvent ev; + ev.type = ClientMessage; + ev.window = w; + ev.message_type = *WM_PROTOCOLS; + ev.format = 32; + ev.data.l[0] = a; + ev.data.l[1] = t; + XSendEvent(dpy, w, False, 0, (XEvent *)&ev); +} + +static void print_coordinates(XIValuatorState *valuators, double *values) { + int n = 0; + for (int i = valuators->mask_len - 1; i >= 0; i--) + if (XIMaskIsSet(valuators->mask, i)) { + n = i+1; + break; + } + bool first = true; + int elt = 0; + for (int i = 0; i < n; i++) { + if (first) + first = false; + else + printf(", "); + if (XIMaskIsSet(valuators->mask, i)) + printf("%.3f", values[elt++]); + else + printf("*"); + } +} + +static double get_axis(XIValuatorState &valuators, int axis) { + if (axis < 0 || !XIMaskIsSet(valuators.mask, axis)) + return 0.0; + double *val = valuators.values; + for (int i = 0; i < axis; i++) + if (XIMaskIsSet(valuators.mask, i)) + val++; + return *val; +} + +void XState::report_xi2_event(XIDeviceEvent *event, const char *type) { + printf("%s (XI2): ", type); + if (event->detail) + printf("%d ", event->detail); + printf("(%.3f, %.3f) - (", event->root_x, event->root_y); + print_coordinates(&event->valuators, event->valuators.values); + printf(") at t = %ld\n", event->time); +} + +void XState::handle_xi2_event(XIDeviceEvent *event) { + switch (event->evtype) { + case XI_ButtonPress: + if (verbosity >= 3) + report_xi2_event(event, "Press"); + if (xinput_pressed.size()) { + if (!current_dev || current_dev->dev != event->deviceid) + break; + } else { + current_app_window.set(get_app_window(event->child)); + if (verbosity >= 3) + printf("Active window 0x%lx -> 0x%lx\n", event->child, current_app_window.get()); + } + current_dev = grabber->get_xi_dev(event->deviceid); + if (!current_dev) { + printf("Warning: Spurious device event\n"); + break; + } + if (current_dev->master) + XISetClientPointer(dpy, None, current_dev->master); + if (!xinput_pressed.size()) { + guint default_mods = grabber->get_default_mods(event->detail); + if (default_mods == AnyModifier || default_mods == (guint)event->mods.base) + modifiers = AnyModifier; + else + modifiers = event->mods.base; + } + xinput_pressed.insert(event->detail); + in_proximity = get_axis(event->valuators, current_dev->proximity_axis); + H->press(event->detail, create_triple(event->root_x, event->root_y, event->time)); + break; + case XI_ButtonRelease: + if (verbosity >= 3) + report_xi2_event(event, "Release"); + if (!current_dev || current_dev->dev != event->deviceid) + break; + xinput_pressed.erase(event->detail); + in_proximity = get_axis(event->valuators, current_dev->proximity_axis); + H->release(event->detail, create_triple(event->root_x, event->root_y, event->time)); + break; + case XI_Motion: + if (verbosity >= 5) + report_xi2_event(event, "Motion"); + if (!current_dev || current_dev->dev != event->deviceid) + break; + H->motion(create_triple(event->root_x, event->root_y, event->time)); + break; + case XI_RawMotion: + in_proximity = get_axis(((XIRawEvent *)event)->valuators, current_dev->proximity_axis); + handle_raw_motion((XIRawEvent *)event); + break; + case XI_HierarchyChanged: + if (grabber->hierarchy_changed((XIHierarchyEvent *)event)) + win->prefs_tab->update_device_list(); + } +} + +void XState::handle_raw_motion(XIRawEvent *event) { + if (!current_dev || current_dev->dev != event->deviceid) + return; + double x = 0.0, y = 0.0; + bool abs_x = current_dev->absolute; + bool abs_y = current_dev->absolute; + int i = 0; + + if (XIMaskIsSet(event->valuators.mask, 0)) + x = event->raw_values[i++]; + else + abs_x = false; + + if (XIMaskIsSet(event->valuators.mask, 1)) + y = event->raw_values[i++]; + else + abs_y = false; + + if (verbosity >= 5) { + printf("Raw motion (XI2): ("); + print_coordinates(&event->valuators, event->raw_values); + printf(") at t = %ld\n", event->time); + } + + H->raw_motion(create_triple(x * current_dev->scale_x, y * current_dev->scale_y, event->time), abs_x, abs_y); +} + +#undef H + +bool XState::handle(Glib::IOCondition) { + while (XPending(dpy)) { + try { + XEvent ev; + XNextEvent(dpy, &ev); + if (!grabber->handle(ev)) + handle_event(ev); + } catch (GrabFailedException &e) { + printf(_("Error: %s\n"), e.what()); + bail_out(); + } + } + return true; +} + +void XState::update_core_mapping() { + unsigned char map[MAX_BUTTONS]; + int n = XGetPointerMapping(dpy, map, MAX_BUTTONS); + core_inv_map.clear(); + for (int i = n-1; i; i--) + if (map[i] == i+1) + core_inv_map.erase(i+1); + else + core_inv_map[map[i]] = i+1; +} + +void XState::fake_core_button(guint b, bool press) { + if (core_inv_map.count(b)) + b = core_inv_map[b]; + XTestFakeButtonEvent(dpy, b, press, CurrentTime); +} + +void XState::fake_click(guint b) { + fake_core_button(b, true); + fake_core_button(b, false); +} + +void Handler::replace_child(Handler *c) { + if (child) + delete child; + child = c; + if (child) + child->parent = this; + if (verbosity >= 2) { + std::string stack; + for (Handler *h = child ? child : this; h; h=h->parent) { + stack = h->name() + " " + stack; + } + printf("New event handling stack: %s\n", stack.c_str()); + } + Handler *new_handler = child ? child : this; + grabber->grab(new_handler->grab_mode()); + if (child) + child->init(); + while (xstate->queued.size() && xstate->idle()) { + (*xstate->queued.begin())(); + xstate->queued.pop_front(); + } +} + +class IgnoreHandler : public Handler { + RModifiers mods; + bool proximity; +public: + IgnoreHandler(RModifiers mods_) : mods(mods_), proximity(xstate->in_proximity && prefs.proximity.get()) {} + virtual void press(guint b, RTriple e) { + if (xstate->current_dev->master) { + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + XTestFakeButtonEvent(dpy, b, true, CurrentTime); + } + } + virtual void motion(RTriple e) { + if (xstate->current_dev->master) + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + if (proximity && !xstate->in_proximity) + parent->replace_child(NULL); + } + virtual void release(guint b, RTriple e) { + if (xstate->current_dev->master) { + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + XTestFakeButtonEvent(dpy, b, false, CurrentTime); + } + if (proximity ? !xstate->in_proximity : !xstate->xinput_pressed.size()) + parent->replace_child(NULL); + } + virtual std::string name() { return "Ignore"; } + virtual Grabber::State grab_mode() { return Grabber::NONE; } +}; + +class ButtonHandler : public Handler { + RModifiers mods; + guint button, real_button; + bool proximity; +public: + ButtonHandler(RModifiers mods_, guint button_) : + mods(mods_), + button(button_), + real_button(0), + proximity(xstate->in_proximity && prefs.proximity.get()) + {} + virtual void press(guint b, RTriple e) { + if (xstate->current_dev->master) { + if (!real_button) + real_button = b; + if (real_button == b) + b = button; + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + XTestFakeButtonEvent(dpy, b, true, CurrentTime); + } + } + virtual void motion(RTriple e) { + if (xstate->current_dev->master) + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + if (proximity && !xstate->in_proximity) + parent->replace_child(NULL); + } + virtual void release(guint b, RTriple e) { + if (xstate->current_dev->master) { + if (real_button == b) + b = button; + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + XTestFakeButtonEvent(dpy, b, false, CurrentTime); + } + if (proximity ? !xstate->in_proximity : !xstate->xinput_pressed.size()) + parent->replace_child(NULL); + } + virtual std::string name() { return "Button"; } + virtual Grabber::State grab_mode() { return Grabber::NONE; } +}; + +void XState::bail_out() { + handler->replace_child(NULL); + xinput_pressed.clear(); + XFlush(dpy); +} + + +int XState::xErrorHandler(Display *dpy2, XErrorEvent *e) { + if (dpy != dpy2) + return xstate->oldHandler(dpy2, e); + if (verbosity == 0 && e->error_code == BadWindow) { + switch (e->request_code) { + case X_ChangeWindowAttributes: + case X_GetProperty: + case X_QueryTree: + return 0; + } + } + if (e->request_code == X_GrabButton || + (grabber && e->request_code == grabber->event && + e->minor_code == X_GrabDeviceButton)) { + if (!xstate->handler || xstate->idle()) { + printf(_("Error: %s\n"), e->request_code==X_GrabButton ? "A grab failed" : "An XInput grab failed"); + } else { + printf(_("Error: A grab failed. Resetting...\n")); + xstate->bail_out(); + } + } else { + char text[64]; + XGetErrorText(dpy, e->error_code, text, sizeof text); + char msg[16]; + snprintf(msg, sizeof msg, "%d", e->request_code); + char def[32]; + snprintf(def, sizeof def, "request_code=%d, minor_code=%d", e->request_code, e->minor_code); + char dbtext[128]; + XGetErrorDatabaseText(dpy, "XRequest", msg, + def, dbtext, sizeof dbtext); + printf("XError: %s: %s\n", text, dbtext); + } + return 0; +} + +int XState::xIOErrorHandler(Display *dpy2) { + if (dpy != dpy2) + return xstate->oldIOHandler(dpy2); + printf("Fatal Error: Connection to X server lost\n"); + quit(); + return 0; +} + +void XState::ping() { + XClientMessageEvent ev; + ev.type = ClientMessage; + ev.window = ping_window; + ev.message_type = *EASYSTROKE_PING; + ev.format = 32; + XSendEvent(dpy, ping_window, False, 0, (XEvent *)&ev); + XFlush(dpy); +} + +void XState::remove_device(int deviceid) { + if (current_dev && current_dev->dev == deviceid) + current_dev = NULL; +} + +void XState::ungrab(int deviceid) { + if (current_dev && current_dev->dev == deviceid) + xinput_pressed.clear(); +} + +class WaitForPongHandler : public Handler, protected Timeout { +public: + WaitForPongHandler() { set_timeout(100); } + virtual void timeout() { + printf("Warning: %s timed out\n", "WaitForPongHandler"); + xstate->bail_out(); + } + virtual void pong() { parent->replace_child(NULL); } + virtual std::string name() { return "WaitForPong"; } + virtual Grabber::State grab_mode() { return parent->grab_mode(); } +}; + +static inline float abs(float x) { return x > 0 ? x : -x; } + +class AbstractScrollHandler : public Handler { + bool have_x, have_y; + float last_x, last_y; + Time last_t; + float offset_x, offset_y; + Glib::ustring str; + int orig_x, orig_y; + +protected: + AbstractScrollHandler() : last_t(0), offset_x(0.0), offset_y(0.0) { + if (!prefs.move_back.get() || xstate->current_dev->absolute) + return; + Window dummy1, dummy2; + int dummy3, dummy4; + unsigned int dummy5; + XQueryPointer(dpy, ROOT, &dummy1, &dummy2, &orig_x, &orig_y, &dummy3, &dummy4, &dummy5); + } + virtual void fake_wheel(int b1, int n1, int b2, int n2) { + for (int i = 0; ifake_click(b1); + for (int i = 0; ifake_click(b2); + } + static float curve(float v) { + return v * exp(log(abs(v))/3); + } +protected: + void move_back() { + if (!prefs.move_back.get() || xstate->current_dev->absolute) + return; + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), orig_x, orig_y, 0); + } +public: + virtual void raw_motion(RTriple e, bool abs_x, bool abs_y) { + float dx = abs_x ? (have_x ? e->x - last_x : 0) : e->x; + float dy = abs_y ? (have_y ? e->y - last_y : 0) : e->y; + + if (abs_x) { + last_x = e->x; + have_x = true; + } + + if (abs_y) { + last_y = e->y; + have_y = true; + } + + if (!last_t) { + last_t = e->t; + return; + } + + if (e->t == last_t) + return; + + int dt = e->t - last_t; + last_t = e->t; + + double factor = (prefs.scroll_invert.get() ? 1.0 : -1.0) * prefs.scroll_speed.get(); + offset_x += factor * curve(dx/dt)*dt/20.0; + offset_y += factor * curve(dy/dt)*dt/10.0; + int b1 = 0, n1 = 0, b2 = 0, n2 = 0; + if (abs(offset_x) > 1.0) { + n1 = (int)floor(abs(offset_x)); + if (offset_x > 0) { + b1 = 7; + offset_x -= n1; + } else { + b1 = 6; + offset_x += n1; + } + } + if (abs(offset_y) > 1.0) { + if (abs(offset_y) < 1.0) + return; + n2 = (int)floor(abs(offset_y)); + if (offset_y > 0) { + b2 = 5; + offset_y -= n2; + } else { + b2 = 4; + offset_y += n2; + } + } + if (n1 || n2) + fake_wheel(b1,n1, b2,n2); + } +}; + +class ScrollHandler : public AbstractScrollHandler { + RModifiers mods; + std::map map; + int orig_x, orig_y; + bool proximity; +public: + ScrollHandler(RModifiers mods_) : mods(mods_) { + // If you want to use buttons > 9, you're on your own.. + map[1] = 0; map[2] = 0; map[3] = 0; map[8] = 0; map[9] = 0; + proximity = xstate->in_proximity && prefs.proximity.get(); + } + virtual void raw_motion(RTriple e, bool abs_x, bool abs_y) { + if (proximity && !xstate->in_proximity) { + parent->replace_child(NULL); + move_back(); + } + if (xstate->xinput_pressed.size()) + AbstractScrollHandler::raw_motion(e, abs_x, abs_y); + } + virtual void press_master(guint b, Time t) { + xstate->fake_core_button(b, false); + } + virtual void release(guint b, RTriple e) { + if ((proximity && xstate->in_proximity) || xstate->xinput_pressed.size()) + return; + parent->replace_child(0); + move_back(); + } + virtual std::string name() { return "Scroll"; } + virtual Grabber::State grab_mode() { return Grabber::RAW; } +}; + +class ScrollAdvancedHandler : public AbstractScrollHandler { + RModifiers m; + guint &rb; +public: + ScrollAdvancedHandler(RModifiers m_, guint &rb_) : m(m_), rb(rb_) {} + virtual void fake_wheel(int b1, int n1, int b2, int n2) { + AbstractScrollHandler::fake_wheel(b1, n1, b2, n2); + rb = 0; + } + virtual void release(guint b, RTriple e) { + Handler *p = parent; + p->replace_child(NULL); + p->release(b, e); + move_back(); + } + virtual void press(guint b, RTriple e) { + Handler *p = parent; + p->replace_child(NULL); + p->press(b, e); + move_back(); + } + virtual std::string name() { return "ScrollAdvanced"; } + virtual Grabber::State grab_mode() { return Grabber::RAW; } +}; + +class AdvancedStrokeActionHandler : public Handler { + RStroke s; +public: + AdvancedStrokeActionHandler(RStroke s_, RTriple e) : s(s_) {} + virtual void press(guint b, RTriple e) { + if (stroke_action) { + s->button = b; + (*stroke_action)(s); + } + } + virtual void release(guint b, RTriple e) { + if (stroke_action) + (*stroke_action)(s); + if (xstate->xinput_pressed.size() == 0) + parent->replace_child(NULL); + } + virtual std::string name() { return "InstantStrokeAction"; } + virtual Grabber::State grab_mode() { return Grabber::NONE; } +}; + +class AdvancedHandler : public Handler { + RTriple e; + guint remap_from, remap_to; + Time click_time; + guint replay_button; + RTriple replay_orig; + std::map as; + std::map rs; + std::map mods; + RModifiers sticky_mods; + guint button1, button2; + RPreStroke replay; + + void show_ranking(guint b, RTriple e) { + if (!rs.count(b)) + return; + Ranking::queue_show(rs[b], e); + rs.erase(b); + } + AdvancedHandler(RStroke s, RTriple e_, guint b1, guint b2, RPreStroke replay_) : + e(e_), remap_from(0), remap_to(0), click_time(0), replay_button(0), + button1(b1), button2(b2), replay(replay_) { + if (s) + actions.get_action_list(grabber->current_class->get())->handle_advanced(s, as, rs, b1, b2); + } +public: + static Handler *create(RStroke s, RTriple e, guint b1, guint b2, RPreStroke replay) { + if (stroke_action && s) + return new AdvancedStrokeActionHandler(s, e); + else + return new AdvancedHandler(s, e, b1, b2, replay); + + } + virtual void init() { + if (replay && replay->size()) { + bool replay_first = !as.count(button2); + PreStroke::iterator i = replay->begin(); + if (replay_first) + press(button2 ? button2 : button1, *i); + while (i != replay->end()) + motion(*i++); + if (!replay_first) + press(button2 ? button2 : button1, e); + } else { + press(button2 ? button2 : button1, e); + } + replay.reset(); + } + virtual void press(guint b, RTriple e) { + if (xstate->current_dev->master) + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + click_time = 0; + if (remap_to) { + xstate->fake_core_button(remap_to, false); + } + remap_from = 0; + remap_to = 0; + replay_button = 0; + guint bb = (b == button1) ? button2 : b; + show_ranking(bb, e); + if (!as.count(bb)) { + sticky_mods.reset(); + if (xstate->current_dev->master) + XTestFakeButtonEvent(dpy, b, true, CurrentTime); + return; + } + RAction act = as[bb]; + if (IS_SCROLL(act)) { + click_time = e->t; + replay_button = b; + replay_orig = e; + RModifiers m = act->prepare(); + sticky_mods.reset(); + return replace_child(new ScrollAdvancedHandler(m, replay_button)); + } + if (IS_IGNORE(act)) { + click_time = e->t; + replay_button = b; + replay_orig = e; + } + IF_BUTTON(act, b2) { + // This is kind of a hack: Store modifiers in + // sticky_mods, so that they are automatically released + // on the next press + sticky_mods = act->prepare(); + remap_from = b; + remap_to = b2; + xstate->fake_core_button(b2, true); + return; + } + mods[b] = act->prepare(); + if (IS_KEY(act)) { + if (mods_equal(sticky_mods, mods[b])) + mods[b] = sticky_mods; + else + sticky_mods = mods[b]; + } else + sticky_mods.reset(); + act->run(); + } + virtual void motion(RTriple e) { + if (replay_button && hypot(replay_orig->x - e->x, replay_orig->y - e->y) > 16) + replay_button = 0; + if (xstate->current_dev->master) + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + } + virtual void release(guint b, RTriple e) { + if (xstate->current_dev->master) + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + if (remap_to) { + xstate->fake_core_button(remap_to, false); + } + guint bb = (b == button1) ? button2 : b; + if (!as.count(bb)) { + sticky_mods.reset(); + if (xstate->current_dev->master) + XTestFakeButtonEvent(dpy, b, false, CurrentTime); + } + if (xstate->xinput_pressed.size() == 0) { + if (e->t < click_time + 250 && b == replay_button) { + sticky_mods.reset(); + mods.clear(); + xstate->fake_click(b); + } + return parent->replace_child(NULL); + } + replay_button = 0; + mods.erase((b == button1) ? button2 : b); + if (remap_from) + sticky_mods.reset(); + remap_from = 0; + } + virtual std::string name() { return "Advanced"; } + virtual Grabber::State grab_mode() { return Grabber::NONE; } +}; + +static void get_timeouts(TimeoutType type, int *init, int *final) { + switch (type) { + case TimeoutOff: + *init = 0; + *final = 0; + break; + case TimeoutConservative: + *init = 750; + *final = 750; + break; + case TimeoutDefault: + *init = 250; + *final = 250; + break; + case TimeoutMedium: + *init = 100; + *final = 100; + break; + case TimeoutAggressive: + *init = 50; + *final = 75; + break; + case TimeoutFlick: + *init = 30; + *final = 50; + break; + default:; + } +} + +class StrokeHandler : public Handler, public sigc::trackable { + guint button; + guint trigger; + RPreStroke cur; + bool is_gesture; + bool drawing; + RTriple last, orig; + bool use_timeout; + int init_timeout, final_timeout, radius; + struct Connection { + sigc::connection c; + double dist; + Connection(StrokeHandler *parent, double dist_, int to) : dist(dist_) { + c = Glib::signal_timeout().connect(sigc::mem_fun(*parent, &StrokeHandler::timeout), to); + } + ~Connection() { c.disconnect(); } + }; + typedef boost::shared_ptr RConnection; + sigc::connection init_connection; + std::vector connections; + + RStroke finish(guint b) { + trace->end(); + XFlush(dpy); + RPreStroke c = cur; + if (!is_gesture || grabber->is_instant(button)) + c.reset(new PreStroke); + return Stroke::create(*c, trigger, b, xstate->modifiers, false); + } + + bool timeout() { + if (verbosity >= 2) + printf("Aborting stroke...\n"); + trace->end(); + RPreStroke c = cur; + if (!is_gesture) + c.reset(new PreStroke); + RStroke s; + if (prefs.timeout_gestures.get() || grabber->is_click_hold(button)) + s = Stroke::create(*c, trigger, 0, xstate->modifiers, true); + parent->replace_child(AdvancedHandler::create(s, last, button, 0, cur)); + XFlush(dpy); + return false; + } + + void do_instant() { + PreStroke ps; + RStroke s = Stroke::create(ps, trigger, button, xstate->modifiers, false); + parent->replace_child(AdvancedHandler::create(s, orig, button, button, cur)); + } + + bool expired(RConnection c, double dist) { + c->dist -= dist; + return c->dist < 0; + } +protected: + void abort_stroke() { + parent->replace_child(AdvancedHandler::create(RStroke(), last, button, 0, cur)); + } + virtual void motion(RTriple e) { + cur->add(e); + float dist = hypot(e->x-orig->x, e->y-orig->y); + if (!is_gesture && dist > 16) { + if (use_timeout && !final_timeout) + return abort_stroke(); + init_connection.disconnect(); + is_gesture = true; + } + if (!drawing && dist > 4 && (!use_timeout || final_timeout)) { + drawing = true; + bool first = true; + for (PreStroke::iterator i = cur->begin(); i != cur->end(); i++) { + Trace::Point p; + p.x = (*i)->x; + p.y = (*i)->y; + if (first) { + trace->start(p); + first = false; + } else { + trace->draw(p); + } + } + } else if (drawing) { + Trace::Point p; + p.x = e->x; + p.y = e->y; + trace->draw(p); + } + if (use_timeout && is_gesture) { + connections.erase(remove_if(connections.begin(), connections.end(), + sigc::bind(sigc::mem_fun(*this, &StrokeHandler::expired), + hypot(e->x - last->x, e->y - last->y))), connections.end()); + connections.push_back(RConnection(new Connection(this, radius, final_timeout))); + } + last = e; + } + + virtual void press(guint b, RTriple e) { + RStroke s = finish(b); + parent->replace_child(AdvancedHandler::create(s, e, button, b, cur)); + } + + virtual void release(guint b, RTriple e) { + RStroke s = finish(0); + + if (prefs.move_back.get() && !xstate->current_dev->absolute) + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), orig->x, orig->y, 0); + else + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); + + if (stroke_action) { + (*stroke_action)(s); + return parent->replace_child(NULL); + } + RRanking ranking; + RAction act = actions.get_action_list(grabber->current_class->get())->handle(s, ranking); + if (!IS_CLICK(act)) + Ranking::queue_show(ranking, e); + if (!act) { + XkbBell(dpy, None, 0, None); + return parent->replace_child(NULL); + } + RModifiers mods = act->prepare(); + if (IS_CLICK(act)) + act = Button::create((Gdk::ModifierType)0, b); + else IF_BUTTON(act, b) + return parent->replace_child(new ButtonHandler(mods, b)); + if (IS_IGNORE(act)) + return parent->replace_child(new IgnoreHandler(mods)); + if (IS_SCROLL(act)) + return parent->replace_child(new ScrollHandler(mods)); + char buf[16]; + snprintf(buf, sizeof(buf), "%d", (int)orig->x); + setenv("EASYSTROKE_X1", buf, 1); + snprintf(buf, sizeof(buf), "%d", (int)orig->y); + setenv("EASYSTROKE_Y1", buf, 1); + snprintf(buf, sizeof(buf), "%d", (int)e->x); + setenv("EASYSTROKE_X2", buf, 1); + snprintf(buf, sizeof(buf), "%d", (int)e->y); + setenv("EASYSTROKE_Y2", buf, 1); + act->run(); + unsetenv("EASYSTROKE_X1"); + unsetenv("EASYSTROKE_Y1"); + unsetenv("EASYSTROKE_X2"); + unsetenv("EASYSTROKE_Y2"); + parent->replace_child(NULL); + } +public: + StrokeHandler(guint b, RTriple e) : + button(b), + trigger(grabber->get_default_button() == (int)b ? 0 : b), + is_gesture(false), + drawing(false), + last(e), + orig(e), + init_timeout(prefs.init_timeout.get()), + final_timeout(prefs.final_timeout.get()), + radius(16) + { + const std::map &dt = prefs.device_timeout.ref(); + std::map::const_iterator j = dt.find(xstate->current_dev->name); + if (j != dt.end()) + get_timeouts(j->second, &init_timeout, &final_timeout); + else + get_timeouts(prefs.timeout_profile.get(), &init_timeout, &final_timeout); + use_timeout = init_timeout; + } + virtual void init() { + if (grabber->is_instant(button)) + return do_instant(); + if (grabber->is_click_hold(button)) { + use_timeout = true; + init_timeout = 500; + final_timeout = 0; + } + cur = PreStroke::create(); + cur->add(orig); + if (!use_timeout) + return; + if (final_timeout && final_timeout < 32 && radius < 16*32/final_timeout) { + radius = 16*32/final_timeout; + final_timeout = final_timeout*radius/16; + } + init_connection = Glib::signal_timeout().connect( + sigc::mem_fun(*this, &StrokeHandler::timeout), init_timeout); + } + ~StrokeHandler() { trace->end(); } + virtual std::string name() { return "Stroke"; } + virtual Grabber::State grab_mode() { return Grabber::NONE; } +}; + +class IdleHandler : public Handler { +protected: + virtual void init() { + xstate->update_core_mapping(); + } + virtual void press(guint b, RTriple e) { + if (current_app_window.get()) + XState::activate_window(current_app_window.get(), e->t); + replace_child(new StrokeHandler(b, e)); + } +public: + IdleHandler(XState *xstate_) { + xstate = xstate_; + } + virtual ~IdleHandler() { + XUngrabKey(dpy, XKeysymToKeycode(dpy,XK_Escape), AnyModifier, ROOT); + } + virtual std::string name() { return "Idle"; } + virtual Grabber::State grab_mode() { return Grabber::BUTTON; } +}; + +class SelectHandler : public Handler { + virtual void press_master(guint b, Time t) { + parent->replace_child(new WaitForPongHandler); + xstate->ping(); + xstate->queue(sigc::ptr_fun(>k_main_quit)); + } +public: + virtual std::string name() { return "Select"; } + virtual Grabber::State grab_mode() { return Grabber::SELECT; } +}; + +void XState::select() { + win->get_window().get_window()->lower(); + handler->top()->replace_child(new SelectHandler); +} + +std::string XState::select_window() { + queue(sigc::mem_fun(this, &XState::select)); + gtk_main(); + win->get_window().raise(); + return grabber->current_class->get(); +} + +XState::XState() : current_dev(NULL), in_proximity(false), accepted(true) { + oldHandler = XSetErrorHandler(xErrorHandler); + oldIOHandler = XSetIOErrorHandler(xIOErrorHandler); + ping_window = XCreateSimpleWindow(dpy, ROOT, 0, 0, 1, 1, 0, 0, 0); + handler = new IdleHandler(this); + handler->init(); +} + +void XState::run_action(RAction act) { + RModifiers mods = act->prepare(); + if (IS_IGNORE(act)) + return handler->replace_child(new IgnoreHandler(mods)); + if (IS_SCROLL(act)) + return handler->replace_child(new ScrollHandler(mods)); + act->run(); +} diff --git a/handler.h b/handler.h new file mode 100644 index 00000000..1bd29b42 --- /dev/null +++ b/handler.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2012, Thomas Jaeger + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef __HANDLER_H__ +#define __HANDLER_H__ +#include "gesture.h" +#include "grabber.h" +#include "actiondb.h" + +class Handler; + +class XState { + friend class Handler; +public: + XState(); + + bool handle(Glib::IOCondition); + void handle_enter_leave(XEvent &ev); + void handle_event(XEvent &ev); + void handle_xi2_event(XIDeviceEvent *event); + void handle_raw_motion(XIRawEvent *event); + void report_xi2_event(XIDeviceEvent *event, const char *type); + + void fake_core_button(guint b, bool press); + void fake_click(guint b); + void update_core_mapping(); + + void remove_device(int deviceid); + void ungrab(int deviceid); + + bool idle(); + void ping(); + void bail_out(); + void select(); + void run_action(RAction act); + void queue(sigc::slot f); + std::string select_window(); + + static void activate_window(Window w, Time t); + static Window get_window(Window w, Atom prop); + static Atom get_atom(Window w, Atom prop); + static bool has_atom(Window w, Atom prop, Atom value); + static void icccm_client_message(Window w, Atom a, Time t); + + Grabber::XiDevice *current_dev; + bool in_proximity; + bool accepted; + std::set xinput_pressed; + guint modifiers; + std::map core_inv_map; +private: + Window ping_window; + Handler *handler; + + static int xErrorHandler(Display *dpy2, XErrorEvent *e); + static int xIOErrorHandler(Display *dpy2); + int (*oldHandler)(Display *, XErrorEvent *); + int (*oldIOHandler)(Display *); + std::list > queued; +}; + +class Handler { +public: + Handler *child; + Handler *parent; + Handler() : child(NULL), parent(NULL) {} + Handler *top() { + if (child) + return child->top(); + else + return this; + } + + virtual void motion(RTriple e) {} + virtual void raw_motion(RTriple e, bool, bool) {} + virtual void press(guint b, RTriple e) {} + virtual void release(guint b, RTriple e) {} + virtual void press_master(guint b, Time t) {} + virtual void pong() {} + void replace_child(Handler *c); + virtual void init() {} + virtual ~Handler() { + if (child) + delete child; + } + virtual std::string name() = 0; + virtual Grabber::State grab_mode() = 0; +}; + +extern XState *xstate; + +#endif diff --git a/main.cc b/main.cc index fac5619a..5442a991 100644 --- a/main.cc +++ b/main.cc @@ -25,17 +25,13 @@ #include "water.h" #include "composite.h" #include "grabber.h" +#include "handler.h" #include #include #include #include -#include -#include -// From #include -// which is not C++-safe -#define X_GrabDeviceButton 17 #include #include @@ -53,22 +49,10 @@ std::string config_dir; Win *win = NULL; Display *dpy; Window ROOT; -bool in_proximity = false; -boost::shared_ptr > stroke_action; -Grabber::XiDevice *current_dev = 0; -std::set xinput_pressed; // TODO get rid of -guint modifiers; - -static Window ping_window = 0; -static boost::shared_ptr trace; -static std::map core_inv_map; - -class Handler; -static Handler *handler = 0; -static ActionDBWatcher *action_watcher = 0; -static int (*oldHandler)(Display *, XErrorEvent *) = 0; -static int (*oldIOHandler)(Display *) = 0; +boost::shared_ptr trace; + +static ActionDBWatcher *action_watcher = 0; static Trace *trace_composite() { try { @@ -103,78 +87,6 @@ static Trace *init_trace() { } -void fake_core_button(guint b, bool press) { - if (core_inv_map.count(b)) - b = core_inv_map[b]; - XTestFakeButtonEvent(dpy, b, press, CurrentTime); -} - -static void fake_click(guint b) { - fake_core_button(b, true); - fake_core_button(b, false); -} - -static std::list > queued; - -class Handler { -protected: - Handler *child; -public: - Handler *parent; - Handler() : child(0), parent(0) {} - Handler *top() { - if (child) - return child->top(); - else - return this; - } - static bool idle() { return !handler->child; } - - virtual void motion(RTriple e) {} - virtual void raw_motion(RTriple e, bool, bool) {} - virtual void press(guint b, RTriple e) {} - virtual void release(guint b, RTriple e) {} - virtual void press_master(guint b, Time t) {} - virtual void pong() {} - void replace_child(Handler *c) { - if (child) - delete child; - child = c; - if (child) - child->parent = this; - if (verbosity >= 2) { - std::string stack; - for (Handler *h = child ? child : this; h; h=h->parent) { - stack = h->name() + " " + stack; - } - printf("New event handling stack: %s\n", stack.c_str()); - } - Handler *new_handler = child ? child : this; - grabber->grab(new_handler->grab_mode()); - if (child) - child->init(); - while (queued.size() && Handler::idle()) { - (*queued.begin())(); - queued.pop_front(); - } - } - virtual void init() {} - virtual ~Handler() { - if (child) - delete child; - } - virtual std::string name() = 0; - virtual Grabber::State grab_mode() = 0; -}; - -void queue(sigc::slot f) { - if (Handler::idle()) { - f(); - XFlush(dpy); - } else - queued.push_back(f); -} - class OSD : public Gtk::Window { static std::list osd_stack; int w; @@ -211,567 +123,6 @@ class OSD : public Gtk::Window { std::list OSD::osd_stack; -class IgnoreHandler : public Handler { - RModifiers mods; - bool proximity; -public: - IgnoreHandler(RModifiers mods_) : mods(mods_), proximity(in_proximity && prefs.proximity.get()) {} - virtual void press(guint b, RTriple e) { - if (current_dev->master) { - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - XTestFakeButtonEvent(dpy, b, true, CurrentTime); - } - } - virtual void motion(RTriple e) { - if (current_dev->master) - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - if (proximity && !in_proximity) - parent->replace_child(NULL); - } - virtual void release(guint b, RTriple e) { - if (current_dev->master) { - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - XTestFakeButtonEvent(dpy, b, false, CurrentTime); - } - if (proximity ? !in_proximity : !xinput_pressed.size()) - parent->replace_child(NULL); - } - virtual std::string name() { return "Ignore"; } - virtual Grabber::State grab_mode() { return Grabber::NONE; } -}; - -class ButtonHandler : public Handler { - RModifiers mods; - guint button, real_button; - bool proximity; -public: - ButtonHandler(RModifiers mods_, guint button_) : - mods(mods_), - button(button_), - real_button(0), - proximity(in_proximity && prefs.proximity.get()) - {} - virtual void press(guint b, RTriple e) { - if (current_dev->master) { - if (!real_button) - real_button = b; - if (real_button == b) - b = button; - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - XTestFakeButtonEvent(dpy, b, true, CurrentTime); - } - } - virtual void motion(RTriple e) { - if (current_dev->master) - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - if (proximity && !in_proximity) - parent->replace_child(NULL); - } - virtual void release(guint b, RTriple e) { - if (current_dev->master) { - if (real_button == b) - b = button; - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - XTestFakeButtonEvent(dpy, b, false, CurrentTime); - } - if (proximity ? !in_proximity : !xinput_pressed.size()) - parent->replace_child(NULL); - } - virtual std::string name() { return "Button"; } - virtual Grabber::State grab_mode() { return Grabber::NONE; } -}; - -static void bail_out(); - -static void bail_out() { - handler->replace_child(NULL); - xinput_pressed.clear(); - XFlush(dpy); -} - -static int xErrorHandler(Display *dpy2, XErrorEvent *e) { - if (dpy != dpy2) - return oldHandler(dpy2, e); - if (verbosity == 0 && e->error_code == BadWindow) { - switch (e->request_code) { - case X_ChangeWindowAttributes: - case X_GetProperty: - case X_QueryTree: - return 0; - } - } - if (e->request_code == X_GrabButton || - (grabber && e->request_code == grabber->event && - e->minor_code == X_GrabDeviceButton)) { - if (!handler || Handler::idle()) { - printf(_("Error: %s\n"), e->request_code==X_GrabButton ? "A grab failed" : "An XInput grab failed"); - } else { - printf(_("Error: A grab failed. Resetting...\n")); - bail_out(); - } - } else { - char text[64]; - XGetErrorText(dpy, e->error_code, text, sizeof text); - char msg[16]; - snprintf(msg, sizeof msg, "%d", e->request_code); - char def[32]; - snprintf(def, sizeof def, "request_code=%d, minor_code=%d", e->request_code, e->minor_code); - char dbtext[128]; - XGetErrorDatabaseText(dpy, "XRequest", msg, - def, dbtext, sizeof dbtext); - printf("XError: %s: %s\n", text, dbtext); - } - return 0; -} - -static int xIOErrorHandler(Display *dpy2) { - if (dpy != dpy2) - return oldIOHandler(dpy2); - printf("Fatal Error: Connection to X server lost\n"); - quit(); - return 0; -} - -static XAtom EASYSTROKE_PING("EASYSTROKE_PING"); -static void ping() { - XClientMessageEvent ev; - ev.type = ClientMessage; - ev.window = ping_window; - ev.message_type = *EASYSTROKE_PING; - ev.format = 32; - XSendEvent(dpy, ping_window, False, 0, (XEvent *)&ev); - XFlush(dpy); -} - -class WaitForPongHandler : public Handler, protected Timeout { -public: - WaitForPongHandler() { set_timeout(100); } - virtual void timeout() { - printf("Warning: %s timed out\n", "WaitForPongHandler"); - bail_out(); - } - virtual void pong() { parent->replace_child(NULL); } - virtual std::string name() { return "WaitForPong"; } - virtual Grabber::State grab_mode() { return parent->grab_mode(); } -}; - -static void update_core_mapping() { - unsigned char map[MAX_BUTTONS]; - int n = XGetPointerMapping(dpy, map, MAX_BUTTONS); - core_inv_map.clear(); - for (int i = n-1; i; i--) - if (map[i] == i+1) - core_inv_map.erase(i+1); - else - core_inv_map[map[i]] = i+1; -} - -static inline float abs(float x) { return x > 0 ? x : -x; } - -class AbstractScrollHandler : public Handler { - bool have_x, have_y; - float last_x, last_y; - Time last_t; - float offset_x, offset_y; - Glib::ustring str; - int orig_x, orig_y; - -protected: - AbstractScrollHandler() : last_t(0), offset_x(0.0), offset_y(0.0) { - if (!prefs.move_back.get() || current_dev->absolute) - return; - Window dummy1, dummy2; - int dummy3, dummy4; - unsigned int dummy5; - XQueryPointer(dpy, ROOT, &dummy1, &dummy2, &orig_x, &orig_y, &dummy3, &dummy4, &dummy5); - } - virtual void fake_wheel(int b1, int n1, int b2, int n2) { - for (int i = 0; iabsolute) - return; - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), orig_x, orig_y, 0); - } -public: - virtual void raw_motion(RTriple e, bool abs_x, bool abs_y) { - float dx = abs_x ? (have_x ? e->x - last_x : 0) : e->x; - float dy = abs_y ? (have_y ? e->y - last_y : 0) : e->y; - - if (abs_x) { - last_x = e->x; - have_x = true; - } - - if (abs_y) { - last_y = e->y; - have_y = true; - } - - if (!last_t) { - last_t = e->t; - return; - } - - if (e->t == last_t) - return; - - int dt = e->t - last_t; - last_t = e->t; - - double factor = (prefs.scroll_invert.get() ? 1.0 : -1.0) * prefs.scroll_speed.get(); - offset_x += factor * curve(dx/dt)*dt/20.0; - offset_y += factor * curve(dy/dt)*dt/10.0; - int b1 = 0, n1 = 0, b2 = 0, n2 = 0; - if (abs(offset_x) > 1.0) { - n1 = (int)floor(abs(offset_x)); - if (offset_x > 0) { - b1 = 7; - offset_x -= n1; - } else { - b1 = 6; - offset_x += n1; - } - } - if (abs(offset_y) > 1.0) { - if (abs(offset_y) < 1.0) - return; - n2 = (int)floor(abs(offset_y)); - if (offset_y > 0) { - b2 = 5; - offset_y -= n2; - } else { - b2 = 4; - offset_y += n2; - } - } - if (n1 || n2) - fake_wheel(b1,n1, b2,n2); - } -}; - -class ScrollHandler : public AbstractScrollHandler { - RModifiers mods; - std::map map; - int orig_x, orig_y; - bool proximity; -public: - ScrollHandler(RModifiers mods_) : mods(mods_) { - // If you want to use buttons > 9, you're on your own.. - map[1] = 0; map[2] = 0; map[3] = 0; map[8] = 0; map[9] = 0; - proximity = in_proximity && prefs.proximity.get(); - } - virtual void raw_motion(RTriple e, bool abs_x, bool abs_y) { - if (proximity && !in_proximity) { - parent->replace_child(NULL); - move_back(); - } - if (xinput_pressed.size()) - AbstractScrollHandler::raw_motion(e, abs_x, abs_y); - } - virtual void press_master(guint b, Time t) { - fake_core_button(b, false); - } - virtual void release(guint b, RTriple e) { - if ((proximity && in_proximity) || xinput_pressed.size()) - return; - parent->replace_child(0); - move_back(); - } - virtual std::string name() { return "Scroll"; } - virtual Grabber::State grab_mode() { return Grabber::RAW; } -}; - -class ScrollAdvancedHandler : public AbstractScrollHandler { - RModifiers m; - guint &rb; -public: - ScrollAdvancedHandler(RModifiers m_, guint &rb_) : m(m_), rb(rb_) {} - virtual void fake_wheel(int b1, int n1, int b2, int n2) { - AbstractScrollHandler::fake_wheel(b1, n1, b2, n2); - rb = 0; - } - virtual void release(guint b, RTriple e) { - Handler *p = parent; - p->replace_child(NULL); - p->release(b, e); - move_back(); - } - virtual void press(guint b, RTriple e) { - Handler *p = parent; - p->replace_child(NULL); - p->press(b, e); - move_back(); - } - virtual std::string name() { return "ScrollAdvanced"; } - virtual Grabber::State grab_mode() { return Grabber::RAW; } -}; - -// Hack so that we don't have to move stuff around so much -static bool mods_equal(RModifiers m1, RModifiers m2); - -class AdvancedStrokeActionHandler : public Handler { - RStroke s; -public: - AdvancedStrokeActionHandler(RStroke s_, RTriple e) : s(s_) {} - virtual void press(guint b, RTriple e) { - if (stroke_action) { - s->button = b; - (*stroke_action)(s); - } - } - virtual void release(guint b, RTriple e) { - if (stroke_action) - (*stroke_action)(s); - if (xinput_pressed.size() == 0) - parent->replace_child(NULL); - } - virtual std::string name() { return "InstantStrokeAction"; } - virtual Grabber::State grab_mode() { return Grabber::NONE; } -}; - -class AdvancedHandler : public Handler { - RTriple e; - guint remap_from, remap_to; - Time click_time; - guint replay_button; - RTriple replay_orig; - std::map as; - std::map rs; - std::map mods; - RModifiers sticky_mods; - guint button1, button2; - RPreStroke replay; - - void show_ranking(guint b, RTriple e) { - if (!rs.count(b)) - return; - Ranking::queue_show(rs[b], e); - rs.erase(b); - } - AdvancedHandler(RStroke s, RTriple e_, guint b1, guint b2, RPreStroke replay_) : - e(e_), remap_from(0), remap_to(0), click_time(0), replay_button(0), - button1(b1), button2(b2), replay(replay_) { - if (s) - actions.get_action_list(grabber->current_class->get())->handle_advanced(s, as, rs, b1, b2); - } -public: - static Handler *create(RStroke s, RTriple e, guint b1, guint b2, RPreStroke replay) { - if (stroke_action && s) - return new AdvancedStrokeActionHandler(s, e); - else - return new AdvancedHandler(s, e, b1, b2, replay); - - } - virtual void init() { - if (replay && replay->size()) { - bool replay_first = !as.count(button2); - PreStroke::iterator i = replay->begin(); - if (replay_first) - press(button2 ? button2 : button1, *i); - while (i != replay->end()) - motion(*i++); - if (!replay_first) - press(button2 ? button2 : button1, e); - } else { - press(button2 ? button2 : button1, e); - } - replay.reset(); - } - virtual void press(guint b, RTriple e) { - if (current_dev->master) - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - click_time = 0; - if (remap_to) { - fake_core_button(remap_to, false); - } - remap_from = 0; - remap_to = 0; - replay_button = 0; - guint bb = (b == button1) ? button2 : b; - show_ranking(bb, e); - if (!as.count(bb)) { - sticky_mods.reset(); - if (current_dev->master) - XTestFakeButtonEvent(dpy, b, true, CurrentTime); - return; - } - RAction act = as[bb]; - if (IS_SCROLL(act)) { - click_time = e->t; - replay_button = b; - replay_orig = e; - RModifiers m = act->prepare(); - sticky_mods.reset(); - return replace_child(new ScrollAdvancedHandler(m, replay_button)); - } - if (IS_IGNORE(act)) { - click_time = e->t; - replay_button = b; - replay_orig = e; - } - IF_BUTTON(act, b2) { - // This is kind of a hack: Store modifiers in - // sticky_mods, so that they are automatically released - // on the next press - sticky_mods = act->prepare(); - remap_from = b; - remap_to = b2; - fake_core_button(b2, true); - return; - } - mods[b] = act->prepare(); - if (IS_KEY(act)) { - if (mods_equal(sticky_mods, mods[b])) - mods[b] = sticky_mods; - else - sticky_mods = mods[b]; - } else - sticky_mods.reset(); - act->run(); - } - virtual void motion(RTriple e) { - if (replay_button && hypot(replay_orig->x - e->x, replay_orig->y - e->y) > 16) - replay_button = 0; - if (current_dev->master) - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - } - virtual void release(guint b, RTriple e) { - if (current_dev->master) - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - if (remap_to) { - fake_core_button(remap_to, false); - } - guint bb = (b == button1) ? button2 : b; - if (!as.count(bb)) { - sticky_mods.reset(); - if (current_dev->master) - XTestFakeButtonEvent(dpy, b, false, CurrentTime); - } - if (xinput_pressed.size() == 0) { - if (e->t < click_time + 250 && b == replay_button) { - sticky_mods.reset(); - mods.clear(); - fake_click(b); - } - return parent->replace_child(NULL); - } - replay_button = 0; - mods.erase((b == button1) ? button2 : b); - if (remap_from) - sticky_mods.reset(); - remap_from = 0; - } - virtual std::string name() { return "Advanced"; } - virtual Grabber::State grab_mode() { return Grabber::NONE; } -}; - -Atom get_atom(Window w, Atom prop) { - Atom actual_type; - int actual_format; - unsigned long nitems, bytes_after; - unsigned char *prop_return = NULL; - - if (XGetWindowProperty(dpy, w, prop, 0, sizeof(Atom), False, XA_ATOM, &actual_type, &actual_format, - &nitems, &bytes_after, &prop_return) != Success) - return None; - if (!prop_return) - return None; - Atom atom = *(Atom *)prop_return; - XFree(prop_return); - return atom; -} - -bool has_atom(Window w, Atom prop, Atom value) { - Atom actual_type; - int actual_format; - unsigned long nitems, bytes_after; - unsigned char *prop_return = NULL; - - if (XGetWindowProperty(dpy, w, prop, 0, sizeof(Atom), False, XA_ATOM, &actual_type, &actual_format, - &nitems, &bytes_after, &prop_return) != Success) - return None; - if (!prop_return) - return None; - Atom *atoms = (Atom *)prop_return; - bool ans = false; - for (unsigned long i = 0; i < nitems; i++) - if (atoms[i] == value) - ans = true; - XFree(prop_return); - return ans; -} - -Window get_window(Window w, Atom prop) { - Atom actual_type; - int actual_format; - unsigned long nitems, bytes_after; - unsigned char *prop_return = NULL; - - if (XGetWindowProperty(dpy, w, prop, 0, sizeof(Atom), False, XA_WINDOW, &actual_type, &actual_format, - &nitems, &bytes_after, &prop_return) != Success) - return None; - if (!prop_return) - return None; - Window ret = *(Window *)prop_return; - XFree(prop_return); - return ret; -} - -static void icccm_client_message(Window w, Atom a, Time t) { - static XAtom WM_PROTOCOLS("WM_PROTOCOLS"); - XClientMessageEvent ev; - ev.type = ClientMessage; - ev.window = w; - ev.message_type = *WM_PROTOCOLS; - ev.format = 32; - ev.data.l[0] = a; - ev.data.l[1] = t; - XSendEvent(dpy, w, False, 0, (XEvent *)&ev); -} - -static void activate_window(Window w, Time t) { - static XAtom _NET_ACTIVE_WINDOW("_NET_ACTIVE_WINDOW"); - static XAtom _NET_WM_WINDOW_TYPE("_NET_WM_WINDOW_TYPE"); - static XAtom _NET_WM_WINDOW_TYPE_DOCK("_NET_WM_WINDOW_TYPE_DOCK"); - static XAtom WM_PROTOCOLS("WM_PROTOCOLS"); - static XAtom WM_TAKE_FOCUS("WM_TAKE_FOCUS"); - - if (w == get_window(ROOT, *_NET_ACTIVE_WINDOW)) - return; - - Atom window_type = get_atom(w, *_NET_WM_WINDOW_TYPE); - if (window_type == *_NET_WM_WINDOW_TYPE_DOCK) - return; - - XWMHints *wm_hints = XGetWMHints(dpy, w); - if (wm_hints) { - bool input = wm_hints->input; - XFree(wm_hints); - if (!input) - return; - } - - if (!has_atom(w, *WM_PROTOCOLS, *WM_TAKE_FOCUS)) - return; - - XWindowAttributes attr; - if (XGetWindowAttributes(dpy, w, &attr) && attr.override_redirect) - return; - - if (verbosity >= 3) - printf("Giving focus to window 0x%lx\n", w); - - icccm_client_message(w, *WM_TAKE_FOCUS, t); -} - void Trace::start(Trace::Point p) { last = p; active = true; @@ -787,283 +138,6 @@ void Trace::end() { end_(); } -static void get_timeouts(TimeoutType type, int *init, int *final) { - switch (type) { - case TimeoutOff: - *init = 0; - *final = 0; - break; - case TimeoutConservative: - *init = 750; - *final = 750; - break; - case TimeoutDefault: - *init = 250; - *final = 250; - break; - case TimeoutMedium: - *init = 100; - *final = 100; - break; - case TimeoutAggressive: - *init = 50; - *final = 75; - break; - case TimeoutFlick: - *init = 30; - *final = 50; - break; - default:; - } -} - -class StrokeHandler : public Handler, public sigc::trackable { - guint button; - guint trigger; - RPreStroke cur; - bool is_gesture; - bool drawing; - RTriple last, orig; - bool use_timeout; - int init_timeout, final_timeout, radius; - struct Connection { - sigc::connection c; - double dist; - Connection(StrokeHandler *parent, double dist_, int to) : dist(dist_) { - c = Glib::signal_timeout().connect(sigc::mem_fun(*parent, &StrokeHandler::timeout), to); - } - ~Connection() { c.disconnect(); } - }; - typedef boost::shared_ptr RConnection; - sigc::connection init_connection; - std::vector connections; - - RStroke finish(guint b) { - trace->end(); - XFlush(dpy); - RPreStroke c = cur; - if (!is_gesture || grabber->is_instant(button)) - c.reset(new PreStroke); - return Stroke::create(*c, trigger, b, modifiers, false); - } - - bool timeout() { - if (verbosity >= 2) - printf("Aborting stroke...\n"); - trace->end(); - RPreStroke c = cur; - if (!is_gesture) - c.reset(new PreStroke); - RStroke s; - if (prefs.timeout_gestures.get() || grabber->is_click_hold(button)) - s = Stroke::create(*c, trigger, 0, modifiers, true); - parent->replace_child(AdvancedHandler::create(s, last, button, 0, cur)); - XFlush(dpy); - return false; - } - - void do_instant() { - PreStroke ps; - RStroke s = Stroke::create(ps, trigger, button, modifiers, false); - parent->replace_child(AdvancedHandler::create(s, orig, button, button, cur)); - } - - bool expired(RConnection c, double dist) { - c->dist -= dist; - return c->dist < 0; - } -protected: - void abort_stroke() { - parent->replace_child(AdvancedHandler::create(RStroke(), last, button, 0, cur)); - } - virtual void motion(RTriple e) { - cur->add(e); - float dist = hypot(e->x-orig->x, e->y-orig->y); - if (!is_gesture && dist > 16) { - if (use_timeout && !final_timeout) - return abort_stroke(); - init_connection.disconnect(); - is_gesture = true; - } - if (!drawing && dist > 4 && (!use_timeout || final_timeout)) { - drawing = true; - bool first = true; - for (PreStroke::iterator i = cur->begin(); i != cur->end(); i++) { - Trace::Point p; - p.x = (*i)->x; - p.y = (*i)->y; - if (first) { - trace->start(p); - first = false; - } else { - trace->draw(p); - } - } - } else if (drawing) { - Trace::Point p; - p.x = e->x; - p.y = e->y; - trace->draw(p); - } - if (use_timeout && is_gesture) { - connections.erase(remove_if(connections.begin(), connections.end(), - sigc::bind(sigc::mem_fun(*this, &StrokeHandler::expired), - hypot(e->x - last->x, e->y - last->y))), connections.end()); - connections.push_back(RConnection(new Connection(this, radius, final_timeout))); - } - last = e; - } - - virtual void press(guint b, RTriple e) { - RStroke s = finish(b); - parent->replace_child(AdvancedHandler::create(s, e, button, b, cur)); - } - - virtual void release(guint b, RTriple e) { - RStroke s = finish(0); - - if (prefs.move_back.get() && !current_dev->absolute) - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), orig->x, orig->y, 0); - else - XTestFakeMotionEvent(dpy, DefaultScreen(dpy), e->x, e->y, 0); - - if (stroke_action) { - (*stroke_action)(s); - return parent->replace_child(NULL); - } - RRanking ranking; - RAction act = actions.get_action_list(grabber->current_class->get())->handle(s, ranking); - if (!IS_CLICK(act)) - Ranking::queue_show(ranking, e); - if (!act) { - XkbBell(dpy, None, 0, None); - return parent->replace_child(NULL); - } - RModifiers mods = act->prepare(); - if (IS_CLICK(act)) - act = Button::create((Gdk::ModifierType)0, b); - else IF_BUTTON(act, b) - return parent->replace_child(new ButtonHandler(mods, b)); - if (IS_IGNORE(act)) - return parent->replace_child(new IgnoreHandler(mods)); - if (IS_SCROLL(act)) - return parent->replace_child(new ScrollHandler(mods)); - char buf[16]; - snprintf(buf, sizeof(buf), "%d", (int)orig->x); - setenv("EASYSTROKE_X1", buf, 1); - snprintf(buf, sizeof(buf), "%d", (int)orig->y); - setenv("EASYSTROKE_Y1", buf, 1); - snprintf(buf, sizeof(buf), "%d", (int)e->x); - setenv("EASYSTROKE_X2", buf, 1); - snprintf(buf, sizeof(buf), "%d", (int)e->y); - setenv("EASYSTROKE_Y2", buf, 1); - act->run(); - unsetenv("EASYSTROKE_X1"); - unsetenv("EASYSTROKE_Y1"); - unsetenv("EASYSTROKE_X2"); - unsetenv("EASYSTROKE_Y2"); - parent->replace_child(NULL); - } -public: - StrokeHandler(guint b, RTriple e) : - button(b), - trigger(grabber->get_default_button() == (int)b ? 0 : b), - is_gesture(false), - drawing(false), - last(e), - orig(e), - init_timeout(prefs.init_timeout.get()), - final_timeout(prefs.final_timeout.get()), - radius(16) - { - const std::map &dt = prefs.device_timeout.ref(); - std::map::const_iterator j = dt.find(current_dev->name); - if (j != dt.end()) - get_timeouts(j->second, &init_timeout, &final_timeout); - else - get_timeouts(prefs.timeout_profile.get(), &init_timeout, &final_timeout); - use_timeout = init_timeout; - } - virtual void init() { - if (grabber->is_instant(button)) - return do_instant(); - if (grabber->is_click_hold(button)) { - use_timeout = true; - init_timeout = 500; - final_timeout = 0; - } - cur = PreStroke::create(); - cur->add(orig); - if (!use_timeout) - return; - if (final_timeout && final_timeout < 32 && radius < 16*32/final_timeout) { - radius = 16*32/final_timeout; - final_timeout = final_timeout*radius/16; - } - init_connection = Glib::signal_timeout().connect( - sigc::mem_fun(*this, &StrokeHandler::timeout), init_timeout); - } - ~StrokeHandler() { trace->end(); } - virtual std::string name() { return "Stroke"; } - virtual Grabber::State grab_mode() { return Grabber::NONE; } -}; - -class IdleHandler : public Handler { -protected: - virtual void init() { - update_core_mapping(); - } - virtual void press(guint b, RTriple e) { - if (current_app_window.get()) - activate_window(current_app_window.get(), e->t); - replace_child(new StrokeHandler(b, e)); - } -public: - virtual ~IdleHandler() { - XUngrabKey(dpy, XKeysymToKeycode(dpy,XK_Escape), AnyModifier, ROOT); - } - virtual std::string name() { return "Idle"; } - virtual Grabber::State grab_mode() { return Grabber::BUTTON; } -}; - -class SelectHandler : public Handler { - virtual void press_master(guint b, Time t) { - parent->replace_child(new WaitForPongHandler); - ping(); - queue(sigc::ptr_fun(>k_main_quit)); - } -public: - static void create() { - win->get_window().get_window()->lower(); - handler->top()->replace_child(new SelectHandler); - } - virtual std::string name() { return "Select"; } - virtual Grabber::State grab_mode() { return Grabber::SELECT; } -}; - -static void do_run_by_name(RAction act) { - RModifiers mods = act->prepare(); - if (IS_IGNORE(act)) - return handler->replace_child(new IgnoreHandler(mods)); - if (IS_SCROLL(act)) - return handler->replace_child(new ScrollHandler(mods)); - act->run(); -} - -static void run_by_name(const char *str, const Glib::RefPtr &cmd_line) { - for (ActionDB::const_iterator i = actions.begin(); i != actions.end(); i++) { - if (i->second.name == std::string(str)) { - if (i->second.action) - queue(sigc::bind(sigc::ptr_fun(do_run_by_name), i->second.action)); - return; - } - } - char *msg; - asprintf(&msg, _("Warning: No action \"%s\" defined\n"), str); - cmd_line->print(msg); - free(msg); -} - void icon_warning() { for (ActionDB::const_iterator i = actions.begin(); i != actions.end(); i++) { Misc *m = dynamic_cast(i->second.action.get()); @@ -1084,10 +158,10 @@ void icon_warning() { void quit() { static bool dead = false; if (dead) - bail_out(); + xstate->bail_out(); dead = true; win->hide(); - queue(sigc::ptr_fun(&Gtk::Main::quit)); + xstate->queue(sigc::ptr_fun(&Gtk::Main::quit)); } class App : public Gtk::Application, Base { @@ -1101,17 +175,12 @@ class App : public Gtk::Application, Base { private: virtual bool local_command_line_vfunc (char**& arguments, int& exit_status); int on_command_line(const Glib::RefPtr &); + void run_by_name(const char *str, const Glib::RefPtr &cmd_line); void usage(const char *me); void version(); void create_config_dir(); - bool handle(Glib::IOCondition); - void handle_enter_leave(XEvent &ev); - void handle_event(XEvent &ev); - void handle_xi2_event(XIDeviceEvent *event); - void handle_raw_motion(XIRawEvent *event); - void report_xi2_event(XIDeviceEvent *event, const char *type); void on_about(const Glib::VariantBase &) { win->show_about(); } void on_quit(const Glib::VariantBase &) { quit(); } @@ -1128,7 +197,7 @@ class ReloadTrace : public Timeout { void timeout() { if (verbosity >= 2) printf("Reloading gesture display\n"); - queue(sigc::mem_fun(*this, &ReloadTrace::reload)); + xstate->queue(sigc::mem_fun(*this, &ReloadTrace::reload)); } void reload() { trace.reset(init_trace()); } } reload_trace; @@ -1215,6 +284,19 @@ bool App::local_command_line_vfunc (char**& arg, int& exit_status) { return false; } +void App::run_by_name(const char *str, const Glib::RefPtr &cmd_line) { + for (ActionDB::const_iterator i = actions.begin(); i != actions.end(); i++) { + if (i->second.name == std::string(str)) { + if (i->second.action) + xstate->queue(sigc::bind(sigc::mem_fun(xstate, &XState::run_action), i->second.action)); + return; + } + } + char *msg; + asprintf(&msg, _("Warning: No action \"%s\" defined\n"), str); + cmd_line->print(msg); + free(msg); +} int App::on_command_line(const Glib::RefPtr &command_line) { int argc; @@ -1287,9 +369,6 @@ void App::on_activate() { bind_textdomain_codeset("easystroke", "UTF-8"); textdomain("easystroke"); - oldHandler = XSetErrorHandler(xErrorHandler); - oldIOHandler = XSetIOErrorHandler(xIOErrorHandler); - create_config_dir(); unsetenv("DESKTOP_AUTOSTART_ID"); dpy = XOpenDisplay(NULL); @@ -1300,12 +379,11 @@ void App::on_activate() { ROOT = DefaultRootWindow(dpy); - ping_window = XCreateSimpleWindow(dpy, ROOT, 0, 0, 1, 1, 0, 0, 0); - prefs.init(); action_watcher = new ActionDBWatcher; action_watcher->init(); + xstate = new XState; grabber = new Grabber; // Force enter events to be generated XGrabPointer(dpy, ROOT, False, 0, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); @@ -1319,12 +397,10 @@ void App::on_activate() { prefs.trace.connect(trace_notify); prefs.color.connect(trace_notify); - handler = new IdleHandler; - handler->init(); XTestGrabControl(dpy, True); Glib::RefPtr io = Glib::IOSource::create(ConnectionNumber(dpy), Glib::IO_IN); - io->connect(sigc::mem_fun(*this, &App::handle)); + io->connect(sigc::mem_fun(*xstate, &XState::handle)); io->attach(); try { widgets = Gtk::Builder::create_from_string(gui_buffer); @@ -1397,208 +473,6 @@ void App::create_config_dir() { config_dir += "/"; } -extern Window get_app_window(Window w); - -void App::handle_enter_leave(XEvent &ev) { - if (ev.xcrossing.mode == NotifyGrab) - return; - if (ev.xcrossing.detail == NotifyInferior) - return; - Window w = ev.xcrossing.window; - if (ev.type == EnterNotify) { - current_app_window.set(get_app_window(w)); - if (verbosity >= 3) - printf("Entered window 0x%lx -> 0x%lx\n", w, current_app_window.get()); - } else printf("Error: Bogus Enter/Leave event\n"); -} - -#define H (handler->top()) -void App::handle_event(XEvent &ev) { - - switch(ev.type) { - case EnterNotify: - case LeaveNotify: - handle_enter_leave(ev); - return; - - case PropertyNotify: - if (current_app_window.get() == ev.xproperty.window && ev.xproperty.atom == XA_WM_CLASS) - current_app_window.notify(); - return; - - case ButtonPress: - if (verbosity >= 3) - printf("Press (master): %d (%d, %d) at t = %ld\n", ev.xbutton.button, ev.xbutton.x, ev.xbutton.y, ev.xbutton.time); - H->press_master(ev.xbutton.button, ev.xbutton.time); - return; - - case ClientMessage: - if (ev.xclient.window != ping_window) - return; - if (ev.xclient.message_type == *EASYSTROKE_PING) { - if (verbosity >= 3) - printf("Pong\n"); - H->pong(); - } - return; - - case MappingNotify: - if (ev.xmapping.request == MappingPointer) - update_core_mapping(); - if (ev.xmapping.request == MappingKeyboard || ev.xmapping.request == MappingModifier) - XRefreshKeyboardMapping(&ev.xmapping); - return; - case GenericEvent: - if (ev.xcookie.extension == grabber->opcode && XGetEventData(dpy, &ev.xcookie)) { - handle_xi2_event((XIDeviceEvent *)ev.xcookie.data); - XFreeEventData(dpy, &ev.xcookie); - } - } -} - -static void print_coordinates(XIValuatorState *valuators, double *values) { - int n = 0; - for (int i = valuators->mask_len - 1; i >= 0; i--) - if (XIMaskIsSet(valuators->mask, i)) { - n = i+1; - break; - } - bool first = true; - int elt = 0; - for (int i = 0; i < n; i++) { - if (first) - first = false; - else - printf(", "); - if (XIMaskIsSet(valuators->mask, i)) - printf("%.3f", values[elt++]); - else - printf("*"); - } -} - -static double get_axis(XIValuatorState &valuators, int axis) { - if (axis < 0 || !XIMaskIsSet(valuators.mask, axis)) - return 0.0; - double *val = valuators.values; - for (int i = 0; i < axis; i++) - if (XIMaskIsSet(valuators.mask, i)) - val++; - return *val; -} - -void App::report_xi2_event(XIDeviceEvent *event, const char *type) { - printf("%s (XI2): ", type); - if (event->detail) - printf("%d ", event->detail); - printf("(%.3f, %.3f) - (", event->root_x, event->root_y); - print_coordinates(&event->valuators, event->valuators.values); - printf(") at t = %ld\n", event->time); -} - -void App::handle_xi2_event(XIDeviceEvent *event) { - switch (event->evtype) { - case XI_ButtonPress: - if (verbosity >= 3) - report_xi2_event(event, "Press"); - if (xinput_pressed.size()) { - if (!current_dev || current_dev->dev != event->deviceid) - break; - } else { - current_app_window.set(get_app_window(event->child)); - if (verbosity >= 3) - printf("Active window 0x%lx -> 0x%lx\n", event->child, current_app_window.get()); - } - current_dev = grabber->get_xi_dev(event->deviceid); - if (!current_dev) { - printf("Warning: Spurious device event\n"); - break; - } - if (current_dev->master) - XISetClientPointer(dpy, None, current_dev->master); - if (!xinput_pressed.size()) { - guint default_mods = grabber->get_default_mods(event->detail); - if (default_mods == AnyModifier || default_mods == (guint)event->mods.base) - modifiers = AnyModifier; - else - modifiers = event->mods.base; - } - xinput_pressed.insert(event->detail); - in_proximity = get_axis(event->valuators, current_dev->proximity_axis); - H->press(event->detail, create_triple(event->root_x, event->root_y, event->time)); - break; - case XI_ButtonRelease: - if (verbosity >= 3) - report_xi2_event(event, "Release"); - if (!current_dev || current_dev->dev != event->deviceid) - break; - xinput_pressed.erase(event->detail); - in_proximity = get_axis(event->valuators, current_dev->proximity_axis); - H->release(event->detail, create_triple(event->root_x, event->root_y, event->time)); - break; - case XI_Motion: - if (verbosity >= 5) - report_xi2_event(event, "Motion"); - if (!current_dev || current_dev->dev != event->deviceid) - break; - H->motion(create_triple(event->root_x, event->root_y, event->time)); - break; - case XI_RawMotion: - if (!current_dev) - break; - in_proximity = get_axis(((XIRawEvent *)event)->valuators, current_dev->proximity_axis); - handle_raw_motion((XIRawEvent *)event); - break; - case XI_HierarchyChanged: - if (grabber->hierarchy_changed((XIHierarchyEvent *)event)) - win->prefs_tab->update_device_list(); - } -} - -void App::handle_raw_motion(XIRawEvent *event) { - if (!current_dev || current_dev->dev != event->deviceid) - return; - double x = 0.0, y = 0.0; - bool abs_x = current_dev->absolute; - bool abs_y = current_dev->absolute; - int i = 0; - - if (XIMaskIsSet(event->valuators.mask, 0)) - x = event->raw_values[i++]; - else - abs_x = false; - - if (XIMaskIsSet(event->valuators.mask, 1)) - y = event->raw_values[i++]; - else - abs_y = false; - - if (verbosity >= 5) { - printf("Raw motion (XI2): ("); - print_coordinates(&event->valuators, event->raw_values); - printf(") at t = %ld\n", event->time); - } - - H->raw_motion(create_triple(x * current_dev->scale_x, y * current_dev->scale_y, event->time), abs_x, abs_y); -} - -#undef H - -bool App::handle(Glib::IOCondition) { - while (XPending(dpy)) { - try { - XEvent ev; - XNextEvent(dpy, &ev); - if (!grabber->handle(ev)) - handle_event(ev); - } catch (GrabFailedException &e) { - printf(_("Error: %s\n"), e.what()); - bail_out(); - } - } - return true; -} - App::~App() { if (win) { delete win; @@ -1621,16 +495,9 @@ int main(int argc, char **argv) { return app.run(argc, argv); } -std::string select_window() { - queue(sigc::ptr_fun(&SelectHandler::create)); - gtk_main(); - win->get_window().raise(); - return grabber->current_class->get(); -} - void Button::run() { grabber->suspend(); - fake_click(button); + xstate->fake_click(button); grabber->resume(); } @@ -1776,7 +643,7 @@ RModifiers SendKey::prepare() { return RModifiers(new Modifiers(mods, ModAction::get_label())); } -static bool mods_equal(RModifiers m1, RModifiers m2) { +bool mods_equal(RModifiers m1, RModifiers m2) { return m1 && m2 && *m1 == *m2; } diff --git a/prefs.cc b/prefs.cc index 14ed52ee..9061870d 100644 --- a/prefs.cc +++ b/prefs.cc @@ -611,7 +611,7 @@ bool Prefs::select_row(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::i } void Prefs::on_add() { - std::string str = select_window(); + std::string str = grabber->select_window(); bool is_new; { Atomic a;