Back to index

easystroke  0.5.5.1
grabber.cc
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2008-2009, Thomas Jaeger <ThJaeger@gmail.com>
00003  *
00004  * Permission to use, copy, modify, and/or distribute this software for any
00005  * purpose with or without fee is hereby granted, provided that the above
00006  * copyright notice and this permission notice appear in all copies.
00007  *
00008  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
00009  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
00010  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
00011  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
00012  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
00013  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
00014  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
00015  */
00016 #include "actiondb.h" // TODO
00017 #include "grabber.h"
00018 #include "main.h"
00019 #include "win.h" // TODO
00020 #include "prefs.h" // TODO
00021 #include <X11/extensions/XTest.h>
00022 #include <xorg/xserver-properties.h>
00023 #include <X11/cursorfont.h>
00024 #include <X11/Xutil.h>
00025 #include <glibmm/i18n.h>
00026 
00027 extern Window get_window(Window w, Atom prop);
00028 extern Source<bool> disabled;
00029 extern Source<Window> current_app_window;
00030 extern Source<ActionListDiff *> stroke_app;
00031 extern bool in_proximity;
00032 
00033 Grabber *grabber = 0;
00034 
00035 static unsigned int ignore_mods[4] = { 0, LockMask, Mod2Mask, LockMask | Mod2Mask };
00036 static unsigned char device_mask_data[2];
00037 static XIEventMask device_mask;
00038 static unsigned char raw_mask_data[3];
00039 static XIEventMask raw_mask;
00040 
00041 template <class X1, class X2> class BiMap {
00042        std::map<X1, X2> map1;
00043        std::map<X2, X1> map2;
00044 public:
00045        void erase1(X1 x1) {
00046               typename std::map<X1, X2>::iterator i1 = map1.find(x1);
00047               if (i1 == map1.end())
00048                      return;
00049               map2.erase(i1->second);
00050               map1.erase(i1->first);
00051        }
00052        void erase2(X2 x2) {
00053               typename std::map<X2, X1>::iterator i2 = map2.find(x2);
00054               if (i2 == map2.end())
00055                      return;
00056               map1.erase(i2->second);
00057               map2.erase(i2->first);
00058        }
00059        void pop(X1 &x1, X2 &x2) {
00060               typename std::map<X1, X2>::reverse_iterator i1 = map1.rbegin();
00061               x1 = i1->first;
00062               x2 = i1->second;
00063               map2.erase(i1->second);
00064               map1.erase(i1->first);
00065        }
00066        void add(X1 x1, X2 x2) {
00067               erase1(x1);
00068               erase2(x2);
00069               map1[x1] = x2;
00070               map2[x2] = x1;
00071        }
00072        bool empty() { return map1.empty(); }
00073        bool contains1(X1 x1) { return map1.find(x1) != map1.end(); }
00074        bool contains2(X2 x2) { return map2.find(x2) != map2.end(); }
00075        X2 find1(X1 x1) { return map1.find(x1)->second; }
00076        X1 find2(X2 x2) { return map2.find(x2)->second; }
00077 };
00078 
00079 Atom XAtom::operator*() {
00080        if (!atom)
00081               atom = XInternAtom(dpy, name, False);
00082        return atom;
00083 }
00084 
00085 BiMap<Window, Window> frame_win;
00086 BiMap<Window, Window> frame_child;
00087 XAtom _NET_FRAME_WINDOW("_NET_FRAME_WINDOW");
00088 XAtom _NET_WM_STATE("_NET_WM_STATE");
00089 XAtom _NET_WM_STATE_HIDDEN("_NET_WM_STATE_HIDDEN");
00090 XAtom _NET_ACTIVE_WINDOW("_NET_ACTIVE_WINDOW");
00091 
00092 BiMap<unsigned int, Window> minimized;
00093 unsigned int minimized_n = 0;
00094 
00095 void get_frame(Window w) {
00096        Window frame = get_window(w, *_NET_FRAME_WINDOW);
00097        if (!frame)
00098               return;
00099        frame_win.add(frame, w);
00100 }
00101 
00102 Children::Children(Window w) : parent(w) {
00103        XSelectInput(dpy, parent, SubstructureNotifyMask);
00104        unsigned int n;
00105        Window dummyw1, dummyw2, *ch;
00106        XQueryTree(dpy, parent, &dummyw1, &dummyw2, &ch, &n);
00107        for (unsigned int i = 0; i < n; i++)
00108               add(ch[i]);
00109        XFree(ch);
00110 }
00111 
00112 bool Children::handle(XEvent &ev) {
00113        switch (ev.type) {
00114               case CreateNotify:
00115                      if (ev.xcreatewindow.parent != parent)
00116                             return false;
00117                      add(ev.xcreatewindow.window);
00118                      return true;
00119               case DestroyNotify:
00120                      frame_child.erase1(ev.xdestroywindow.window);
00121                      frame_child.erase2(ev.xdestroywindow.window);
00122                      minimized.erase2(ev.xdestroywindow.window);
00123                      destroy(ev.xdestroywindow.window);
00124                      return true;
00125               case ReparentNotify:
00126                      if (ev.xreparent.event != parent)
00127                             return false;
00128                      if (ev.xreparent.window == parent)
00129                             return false;
00130                      if (ev.xreparent.parent == parent)
00131                             add(ev.xreparent.window);
00132                      else
00133                             remove(ev.xreparent.window);
00134                      return true;
00135               case PropertyNotify:
00136                      if (ev.xproperty.atom == *_NET_FRAME_WINDOW) {
00137                             if (ev.xproperty.state == PropertyDelete)
00138                                    frame_win.erase1(ev.xproperty.window);
00139                             if (ev.xproperty.state == PropertyNewValue)
00140                                    get_frame(ev.xproperty.window);
00141                             return true;
00142                      }
00143                      if (ev.xproperty.atom == *_NET_WM_STATE) {
00144                             if (ev.xproperty.state == PropertyDelete) {
00145                                    minimized.erase2(ev.xproperty.window);
00146                                    return true;
00147                             }
00148                             if (has_atom(ev.xproperty.window, *_NET_WM_STATE, *_NET_WM_STATE_HIDDEN))
00149                                    minimized.add(minimized_n++, ev.xproperty.window);
00150                             else
00151                                    minimized.erase2(ev.xproperty.window);
00152                             return true;
00153                      }
00154                      return false;
00155               default:
00156                      return false;
00157        }
00158 }
00159 
00160 void Children::add(Window w) {
00161        if (!w)
00162               return;
00163 
00164        XSelectInput(dpy, w, EnterWindowMask | PropertyChangeMask);
00165        get_frame(w);
00166 }
00167 
00168 void Children::remove(Window w) {
00169        XSelectInput(dpy, w, 0);
00170        destroy(w);
00171 }
00172 
00173 void Children::destroy(Window w) {
00174        frame_win.erase1(w);
00175        frame_win.erase2(w);
00176 }
00177 
00178 void activate(Window w, Time t) {
00179        XClientMessageEvent ev;
00180        ev.type = ClientMessage;
00181        ev.window = w;
00182        ev.message_type = *_NET_ACTIVE_WINDOW;
00183        ev.format = 32;
00184        ev.data.l[0] = 0; // 1 app, 2 pager
00185        ev.data.l[1] = t;
00186        ev.data.l[2] = 0;
00187        ev.data.l[3] = 0;
00188        ev.data.l[4] = 0;
00189        XSendEvent(dpy, ROOT, False, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent *)&ev);
00190 }
00191 
00192 std::string get_wm_class(Window w, ActionListDiff *actions) {
00193        if (actions && actions->app)
00194               return actions->name;
00195        if (!w)
00196               return "";
00197        XClassHint ch;
00198        if (!XGetClassHint(dpy, w, &ch))
00199               return "";
00200        std::string ans = ch.res_name;
00201        XFree(ch.res_name);
00202        XFree(ch.res_class);
00203        return ans;
00204 }
00205 
00206 class IdleNotifier : public Base {
00207        sigc::slot<void> f;
00208        void run() { f(); }
00209 public:
00210        IdleNotifier(sigc::slot<void> f_) : f(f_) {}
00211        virtual void notify() { queue(sigc::mem_fun(*this, &IdleNotifier::run)); }
00212 };
00213 
00214 void Grabber::unminimize() {
00215        if (minimized.empty())
00216               return;
00217        Window w;
00218        unsigned int n;
00219        minimized.pop(n, w);
00220        activate(w, CurrentTime);
00221 }
00222 
00223 const char *Grabber::state_name[4] = { "None", "Button", "Select", "Raw" };
00224 
00225 Grabber::Grabber() : children(ROOT) {
00226        current = BUTTON;
00227        suspended = 0;
00228        suspend();
00229        active = true;
00230        grabbed = NONE;
00231        xi_grabbed = false;
00232        xi_devs_grabbed = GrabNo;
00233        grabbed_button.button = 0;
00234        grabbed_button.state = 0;
00235        cursor_select = XCreateFontCursor(dpy, XC_crosshair);
00236        init_xi();
00237        prefs.excluded_devices.connect(new IdleNotifier(sigc::mem_fun(*this, &Grabber::update)));
00238        prefs.button.connect(new IdleNotifier(sigc::mem_fun(*this, &Grabber::update)));
00239        current_class = fun2(&get_wm_class, current_app_window, stroke_app);
00240        current_class->connect(new IdleNotifier(sigc::mem_fun(*this, &Grabber::update)));
00241        disabled.connect(new IdleNotifier(sigc::mem_fun(*this, &Grabber::set)));
00242        update();
00243        resume();
00244 }
00245 
00246 Grabber::~Grabber() {
00247        XFreeCursor(dpy, cursor_select);
00248 }
00249 
00250 bool Grabber::init_xi() {
00251        /* XInput Extension available? */
00252        int major = 2, minor = 0;
00253        if (!XQueryExtension(dpy, "XInputExtension", &opcode, &event, &error) ||
00254                      XIQueryVersion(dpy, &major, &minor) == BadRequest ||
00255                      major < 2) {
00256               printf("Error: This version of easystroke needs an XInput 2.0-aware X server.\n"
00257                             "Please downgrade to easystroke 0.4.x or upgrade your X server to 1.7.\n");
00258               exit(EXIT_FAILURE);
00259        }
00260 
00261        int n;
00262        XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &n);
00263        if (!info) {
00264               printf("Warning: No XInput devices available\n");
00265               return false;
00266        }
00267 
00268        current_dev = NULL;
00269 
00270        for (int i = 0; i < n; i++)
00271               new_device(info + i);
00272        XIFreeDeviceInfo(info);
00273        prefs.excluded_devices.connect(new IdleNotifier(sigc::mem_fun(*this, &Grabber::update_excluded)));
00274        update_excluded();
00275        xi_grabbed = false;
00276        set();
00277 
00278        if (!xi_devs.size()) {
00279               printf("Error: No suitable XInput devices found\n");
00280               exit(EXIT_FAILURE);
00281        }
00282 
00283        // TODO: Proximity
00284        device_mask.deviceid = XIAllDevices;
00285        device_mask.mask = device_mask_data;
00286        device_mask.mask_len = sizeof(device_mask_data);
00287        memset(device_mask.mask, 0, device_mask.mask_len);
00288        XISetMask(device_mask.mask, XI_ButtonPress);
00289        XISetMask(device_mask.mask, XI_ButtonRelease);
00290        XISetMask(device_mask.mask, XI_Motion);
00291 
00292        raw_mask.deviceid = XIAllDevices;
00293        raw_mask.mask = raw_mask_data;
00294        raw_mask.mask_len = sizeof(raw_mask_data);
00295        memset(raw_mask.mask, 0, raw_mask.mask_len);
00296        XISetMask(raw_mask.mask, XI_ButtonPress);
00297        XISetMask(raw_mask.mask, XI_ButtonRelease);
00298        XISetMask(raw_mask.mask, XI_RawMotion);
00299 
00300        XIEventMask global_mask;
00301        unsigned char data[2] = { 0, 0 };
00302        global_mask.deviceid = XIAllDevices;
00303        global_mask.mask = data;
00304        global_mask.mask_len = sizeof(data);
00305        XISetMask(global_mask.mask, XI_HierarchyChanged);
00306 
00307        XISelectEvents(dpy, ROOT, &global_mask, 1);
00308 
00309        return true;
00310 }
00311 
00312 void Grabber::hierarchy_changed(XIHierarchyEvent *event) {
00313        for (int i = 0; i < event->num_info; i++) {
00314               XIHierarchyInfo *info = event->info + i;
00315               if (info->flags & XISlaveAdded) {
00316                      int n;
00317                      XIDeviceInfo *dev_info = XIQueryDevice(dpy, info->deviceid, &n);
00318                      if (!dev_info)
00319                             return;
00320                      new_device(dev_info);
00321                      XIFreeDeviceInfo(dev_info);
00322                      update_excluded();
00323                      win->prefs_tab->update_device_list();
00324               } else if (info->flags & XISlaveRemoved) {
00325                      if (verbosity >= 1)
00326                             printf("Device %d removed.\n", info->deviceid);
00327                      xi_devs.erase(info->deviceid);
00328                      win->prefs_tab->update_device_list();
00329                      if (current_dev && current_dev->dev == info->deviceid)
00330                             current_dev = NULL;
00331               } else if (info->flags & (XISlaveAttached | XISlaveDetached)) {
00332                      DeviceMap::iterator i = xi_devs.find(info->deviceid);
00333                      if (i == xi_devs.end())
00334                             return;
00335                      i->second->master = info->attachment;
00336               }
00337        }
00338 }
00339 
00340 void Grabber::update_excluded() {
00341        suspend();
00342        for (DeviceMap::iterator i = xi_devs.begin(); i != xi_devs.end(); ++i)
00343               i->second->active = !prefs.excluded_devices.ref().count(i->second->name);
00344        resume();
00345 }
00346 
00347 bool is_xtest_device(int dev) {
00348        static XAtom XTEST(XI_PROP_XTEST_DEVICE);
00349        Atom type;
00350        int format;
00351        unsigned long num_items, bytes_after;
00352        unsigned char *data;
00353        if (Success != XIGetProperty(dpy, dev, *XTEST, 0, 1, False, XA_INTEGER,
00354                             &type, &format, &num_items, &bytes_after, &data))
00355               return false;
00356        bool ret = num_items && format == 8 && *((int8_t*)data);
00357        XFree(data);
00358        return ret;
00359 }
00360 
00361 void Grabber::new_device(XIDeviceInfo *info) {
00362        if (info->use == XIMasterPointer || info->use == XIMasterKeyboard)
00363               return;
00364 
00365        if (is_xtest_device(info->deviceid))
00366               return;
00367 
00368        for (int j = 0; j < info->num_classes; j++)
00369               if (info->classes[j]->type == ButtonClass) {
00370                      XiDevice *xi_dev = new XiDevice(this, info);
00371                      xi_devs[info->deviceid].reset(xi_dev);
00372                      return;
00373               }
00374 }
00375 
00376 Grabber::XiDevice::XiDevice(Grabber *parent, XIDeviceInfo *info) : supports_pressure(false), absolute(false), scale_x(1.0), scale_y(1.0), num_buttons(0) {
00377        dev = info->deviceid;
00378        name = info->name;
00379        master = info->attachment;
00380        for (int j = 0; j < info->num_classes; j++) {
00381               XIAnyClassInfo *dev_class = info->classes[j];
00382               if (dev_class->type == ButtonClass) {
00383                      XIButtonClassInfo *b = (XIButtonClassInfo*)dev_class;
00384                      num_buttons = b->num_buttons;
00385               } else if (dev_class->type == ValuatorClass) {
00386                      XIValuatorClassInfo *v = (XIValuatorClassInfo*)dev_class;
00387                      if ((v->number == 0 || v->number == 1) && v->mode != XIModeRelative) {
00388                             absolute = true;
00389                             if (v-> number == 0)
00390                                    scale_x = (double)DisplayWidth(dpy, DefaultScreen(dpy)) / (double)(v->max - v->min);
00391                             else
00392                                    scale_y = (double)DisplayHeight(dpy, DefaultScreen(dpy)) / (double)(v->max - v->min);
00393 
00394                      }
00395                      if (v->number == 2) {
00396                             pressure_min = v->min;
00397                             pressure_max = v->max;
00398                             if (pressure_min < pressure_max)
00399                                    supports_pressure = true;
00400                      }
00401               }
00402        }
00403 
00404        if (verbosity >= 1)
00405               printf("Opened Device %d ('%s'%s).\n", dev, info->name,
00406                             absolute ?
00407                                    supports_pressure ? ": absolute, pressure" : ": absolute" :
00408                                    supports_pressure ? ": pressure" : "");
00409 }
00410 
00411 Grabber::XiDevice *Grabber::get_xi_dev(int id) {
00412        DeviceMap::iterator i = xi_devs.find(id);
00413        return i == xi_devs.end() ? NULL : i->second.get();
00414 }
00415 
00416 void Grabber::XiDevice::grab_button(ButtonInfo &bi, bool grab) {
00417        XIGrabModifiers modifiers[4] = {{0,0},{0,0},{0,0},{0,0}};
00418        int nmods = 0;
00419        if (bi.button == AnyModifier) {
00420               nmods = 1;
00421               modifiers[0].modifiers = XIAnyModifier;
00422        } else {
00423               nmods = 4;
00424               for (int i = 0; i < 4; i++)
00425                      modifiers[i].modifiers = bi.state ^ ignore_mods[i];
00426        }
00427        if (grab)
00428               XIGrabButton(dpy, dev, bi.button, ROOT, None, GrabModeAsync, GrabModeAsync, False, &device_mask, nmods, modifiers);
00429        else {
00430               XIUngrabButton(dpy, dev, bi.button, ROOT, nmods, modifiers);
00431               if (current_dev && current_dev->dev == dev)
00432                      xinput_pressed.clear();
00433        }
00434 }
00435 
00436 void Grabber::grab_xi(bool grab) {
00437        if (!xi_grabbed == !grab)
00438               return;
00439        xi_grabbed = grab;
00440        for (DeviceMap::iterator i = xi_devs.begin(); i != xi_devs.end(); ++i)
00441               if (i->second->active)
00442                      for (std::vector<ButtonInfo>::iterator j = buttons.begin(); j != buttons.end(); j++)
00443                             i->second->grab_button(*j, grab);
00444 }
00445 
00446 void Grabber::XiDevice::grab_device(GrabState grab) {
00447        if (grab == GrabNo) {
00448               XIUngrabDevice(dpy, dev, CurrentTime);
00449               if (current_dev && current_dev->dev == dev)
00450                      xinput_pressed.clear();
00451               return;
00452        }
00453        XIGrabDevice(dpy, dev, ROOT, CurrentTime, None, GrabModeAsync, GrabModeAsync, False,
00454                      grab == GrabYes ? &device_mask : &raw_mask);
00455 }
00456 
00457 void Grabber::grab_xi_devs(GrabState grab) {
00458        if (xi_devs_grabbed == grab)
00459               return;
00460        xi_devs_grabbed = grab;
00461        for (DeviceMap::iterator i = xi_devs.begin(); i != xi_devs.end(); ++i)
00462               i->second->grab_device(grab);
00463 }
00464 
00465 void Grabber::set() {
00466        bool act = !suspended && ((active && !disabled.get()) || (current != NONE && current != BUTTON));
00467        grab_xi(act && current != SELECT);
00468        if (!act)
00469               grab_xi_devs(GrabNo);
00470        else if (current == NONE)
00471               grab_xi_devs(GrabYes);
00472        else if (current == RAW)
00473               grab_xi_devs(GrabRaw);
00474        else
00475               grab_xi_devs(GrabNo);
00476        State old = grabbed;
00477        grabbed = act ? current : NONE;
00478        if (old == grabbed)
00479               return;
00480        if (verbosity >= 2)
00481               printf("grabbing: %s\n", state_name[grabbed]);
00482 
00483        if (old == SELECT)
00484               XUngrabPointer(dpy, CurrentTime);
00485 
00486        if (grabbed == SELECT) {
00487               int code = XGrabPointer(dpy, ROOT, False, ButtonPressMask,
00488                             GrabModeAsync, GrabModeAsync, ROOT, cursor_select, CurrentTime);
00489               if (code != GrabSuccess)
00490                      throw GrabFailedException(code);
00491        }
00492 }
00493 
00494 bool Grabber::is_grabbed(guint b) {
00495        for (std::vector<ButtonInfo>::iterator i = buttons.begin(); i != buttons.end(); i++)
00496               if (i->button == b)
00497                      return true;
00498        return false;
00499 }
00500 
00501 bool Grabber::is_instant(guint b) {
00502        for (std::vector<ButtonInfo>::iterator i = buttons.begin(); i != buttons.end(); i++)
00503               if (i->button == b && i->instant)
00504                      return true;
00505        return false;
00506 }
00507 
00508 bool Grabber::is_click_hold(guint b) {
00509        for (std::vector<ButtonInfo>::iterator i = buttons.begin(); i != buttons.end(); i++)
00510               if (i->button == b && i->click_hold)
00511                      return true;
00512        return false;
00513 }
00514 
00515 int get_default_button() {
00516        if (grabber)
00517               return grabber->get_default_button();
00518        else
00519               return prefs.button.get().button;
00520 }
00521 
00522 void Grabber::update() {
00523        std::map<std::string, RButtonInfo>::const_iterator i = prefs.exceptions.ref().find(current_class->get());
00524        active = true;
00525        ButtonInfo bi = prefs.button.ref();
00526        if (i != prefs.exceptions.ref().end()) {
00527               if (i->second)
00528                      bi = *i->second;
00529               else
00530                      active = false;
00531        }
00532        const ActionListDiff *a = stroke_app.get();
00533        if (!a)
00534               a = actions.get_action_list(current_class->get());
00535        if (active && actions.get_root()->size_rec() && !a->count_actions())
00536               active = false;
00537        const std::vector<ButtonInfo> &extra = prefs.extra_buttons.ref();
00538        if (grabbed_button == bi && buttons.size() == extra.size() + 1 &&
00539                      std::equal(extra.begin(), extra.end(), ++buttons.begin())) {
00540               set();
00541               return;
00542        }
00543        suspend();
00544        grabbed_button = bi;
00545        buttons.clear();
00546        buttons.reserve(extra.size() + 1);
00547        buttons.push_back(bi);
00548        for (std::vector<ButtonInfo>::const_iterator i = extra.begin(); i != extra.end(); ++i)
00549               if (!i->overlap(bi))
00550                      buttons.push_back(*i);
00551        resume();
00552 }
00553 
00554 // Fuck Xlib
00555 bool has_wm_state(Window w) {
00556        static XAtom WM_STATE("WM_STATE");
00557        Atom actual_type_return;
00558        int actual_format_return;
00559        unsigned long nitems_return;
00560        unsigned long bytes_after_return;
00561        unsigned char *prop_return;
00562        if (Success != XGetWindowProperty(dpy, w, *WM_STATE, 0, 2, False,
00563                             AnyPropertyType, &actual_type_return,
00564                             &actual_format_return, &nitems_return,
00565                             &bytes_after_return, &prop_return))
00566               return false;
00567        XFree(prop_return);
00568        return nitems_return;
00569 }
00570 
00571 Window find_wm_state(Window w) {
00572        if (!w)
00573               return w;
00574        if (has_wm_state(w))
00575               return w;
00576        Window found = None;
00577        unsigned int n;
00578        Window dummyw1, dummyw2, *ch;
00579        if (!XQueryTree(dpy, w, &dummyw1, &dummyw2, &ch, &n))
00580               return None;
00581        for (unsigned int i = 0; i != n; i++)
00582               if (has_wm_state(ch[i]))
00583                      found = ch[i];
00584        if (!found)
00585               for (unsigned int i = 0; i != n; i++) {
00586                      found = find_wm_state(ch[i]);
00587                      if (found)
00588                             break;
00589               }
00590        XFree(ch);
00591        return found;
00592 }
00593 
00594 Window get_app_window(Window w) {
00595        if (!w)
00596               return w;
00597 
00598        if (frame_win.contains1(w))
00599               return frame_win.find1(w);
00600 
00601        if (frame_child.contains1(w))
00602               return frame_child.find1(w);
00603 
00604        Window w2 = find_wm_state(w);
00605        if (w2) {
00606               frame_child.add(w, w2);
00607               if (w2 != w) {
00608                      w = w2;
00609                      XSelectInput(dpy, w2, StructureNotifyMask | PropertyChangeMask);
00610               }
00611               return w2;
00612        }
00613        if (verbosity >= 1)
00614               printf("Window 0x%lx does not have an associated top-level window\n", w);
00615        return w;
00616 }