1 /*
2  * Copyright 2007 Kim woelders
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that copyright
7  * notice and this permission notice appear in supporting documentation, and
8  * that the name of the copyright holders not be used in advertising or
9  * publicity pertaining to distribution of the software without specific,
10  * written prior permission.  The copyright holders make no representations
11  * about the suitability of this software for any purpose.  It is provided "as
12  * is" without express or implied warranty.
13  *
14  * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16  * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20  * OF THIS SOFTWARE.
21  */
22 #include <xcb/xcb.h>
23 #include <xcb/xproto.h>
24 
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "clientwin.h"
29 #include "dsimple.h"
30 
31 static xcb_atom_t atom_wm_state = XCB_ATOM_NONE;
32 
33 /*
34  * Check if window has given property
35  */
36 static Bool
Window_Has_Property(xcb_connection_t * dpy,xcb_window_t win,xcb_atom_t atom)37 Window_Has_Property(xcb_connection_t * dpy, xcb_window_t win, xcb_atom_t atom)
38 {
39     xcb_get_property_cookie_t prop_cookie;
40     xcb_get_property_reply_t *prop_reply;
41 
42     prop_cookie = xcb_get_property (dpy, False, win, atom,
43                                     XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
44 
45     prop_reply = xcb_get_property_reply (dpy, prop_cookie, NULL);
46 
47     if (prop_reply) {
48         xcb_atom_t reply_type = prop_reply->type;
49         free (prop_reply);
50         if (reply_type != XCB_NONE)
51             return True;
52     }
53 
54     return False;
55 }
56 
57 /*
58  * Check if window is viewable
59  */
60 static Bool
Window_Is_Viewable(xcb_connection_t * dpy,xcb_window_t win)61 Window_Is_Viewable(xcb_connection_t * dpy, xcb_window_t win)
62 {
63     Bool ok = False;
64     xcb_get_window_attributes_cookie_t attr_cookie;
65     xcb_get_window_attributes_reply_t *xwa;
66 
67     attr_cookie = xcb_get_window_attributes (dpy, win);
68     xwa = xcb_get_window_attributes_reply (dpy, attr_cookie, NULL);
69 
70     if (xwa) {
71         ok = (xwa->_class == XCB_WINDOW_CLASS_INPUT_OUTPUT) &&
72             (xwa->map_state == XCB_MAP_STATE_VIEWABLE);
73         free (xwa);
74     }
75 
76     return ok;
77 }
78 
79 /*
80  * Find a window that has WM_STATE set in the window tree below win.
81  * Unmapped/unviewable windows are not considered valid matches.
82  * Children are searched in top-down stacking order.
83  * The first matching window is returned, None if no match is found.
84  */
85 static xcb_window_t
Find_Client_In_Children(xcb_connection_t * dpy,xcb_window_t win)86 Find_Client_In_Children(xcb_connection_t * dpy, xcb_window_t win)
87 {
88     xcb_query_tree_cookie_t qt_cookie;
89     xcb_query_tree_reply_t *tree;
90     xcb_window_t *children;
91     unsigned int n_children;
92     int i;
93 
94     qt_cookie = xcb_query_tree (dpy, win);
95     tree = xcb_query_tree_reply (dpy, qt_cookie, NULL);
96     if (!tree)
97         return XCB_WINDOW_NONE;
98     n_children = xcb_query_tree_children_length (tree);
99     if (!n_children) {
100         free (tree);
101         return XCB_WINDOW_NONE;
102     }
103     children = xcb_query_tree_children (tree);
104 
105     /* Check each child for WM_STATE and other validity */
106     win = XCB_WINDOW_NONE;
107     for (i = (int) n_children - 1; i >= 0; i--) {
108         if (!Window_Is_Viewable(dpy, children[i])) {
109             /* Don't bother descending into this one */
110             children[i] = XCB_WINDOW_NONE;
111             continue;
112         }
113         if (!Window_Has_Property(dpy, children[i], atom_wm_state))
114             continue;
115 
116         /* Got one */
117         win = children[i];
118         goto done;
119     }
120 
121     /* No children matched, now descend into each child */
122     for (i = (int) n_children - 1; i >= 0; i--) {
123         if (children[i] == XCB_WINDOW_NONE)
124             continue;
125         win = Find_Client_In_Children(dpy, children[i]);
126         if (win != XCB_WINDOW_NONE)
127             break;
128     }
129 
130   done:
131     free (tree); /* includes children */
132 
133     return win;
134 }
135 
136 /*
137  * Find virtual roots (_NET_VIRTUAL_ROOTS)
138  */
139 static xcb_window_t *
Find_Roots(xcb_connection_t * dpy,xcb_window_t root,unsigned int * num)140 Find_Roots(xcb_connection_t * dpy, xcb_window_t root, unsigned int *num)
141 {
142     xcb_atom_t atom_virtual_root;
143 
144     xcb_get_property_cookie_t prop_cookie;
145     xcb_get_property_reply_t *prop_reply;
146 
147     xcb_window_t *prop_ret = NULL;
148 
149     *num = 0;
150 
151     atom_virtual_root = Get_Atom (dpy, "_NET_VIRTUAL_ROOTS");
152     if (atom_virtual_root == XCB_ATOM_NONE)
153         return NULL;
154 
155     prop_cookie = xcb_get_property (dpy, False, root, atom_virtual_root,
156                                     XCB_ATOM_WINDOW, 0, 0x7fffffff);
157     prop_reply = xcb_get_property_reply (dpy, prop_cookie, NULL);
158     if (!prop_reply)
159         return NULL;
160 
161     if ((prop_reply->value_len > 0) && (prop_reply->type == XCB_ATOM_WINDOW)
162         && (prop_reply->format == 32)) {
163         int length = xcb_get_property_value_length (prop_reply);
164         prop_ret = malloc(length);
165         if (prop_ret) {
166             memcpy (prop_ret, xcb_get_property_value(prop_reply), length);
167             *num = prop_reply->value_len;
168         }
169     }
170     free (prop_reply);
171 
172     return prop_ret;
173 }
174 
175 /*
176  * Find child window at pointer location
177  */
178 static xcb_window_t
Find_Child_At_Pointer(xcb_connection_t * dpy,xcb_window_t win)179 Find_Child_At_Pointer(xcb_connection_t * dpy, xcb_window_t win)
180 {
181     xcb_window_t child_return = XCB_WINDOW_NONE;
182 
183     xcb_query_pointer_cookie_t qp_cookie;
184     xcb_query_pointer_reply_t *qp_reply;
185 
186     qp_cookie = xcb_query_pointer (dpy, win);
187     qp_reply = xcb_query_pointer_reply (dpy, qp_cookie, NULL);
188 
189     if (qp_reply) {
190         child_return = qp_reply->child;
191         free (qp_reply);
192     }
193 
194     return child_return;
195 }
196 
197 /*
198  * Find client window at pointer location
199  *
200  * root   is the root window.
201  * subwin is the subwindow reported by a ButtonPress event on root.
202  *
203  * If the WM uses virtual roots subwin may be a virtual root.
204  * If so, we descend the window stack at the pointer location and assume the
205  * child is the client or one of its WM frame windows.
206  * This will of course work only if the virtual roots are children of the real
207  * root.
208  */
209 xcb_window_t
Find_Client(xcb_connection_t * dpy,xcb_window_t root,xcb_window_t subwin)210 Find_Client(xcb_connection_t * dpy, xcb_window_t root, xcb_window_t subwin)
211 {
212     xcb_window_t *roots;
213     unsigned int i, n_roots;
214     xcb_window_t win;
215 
216     /* Check if subwin is a virtual root */
217     roots = Find_Roots(dpy, root, &n_roots);
218     for (i = 0; i < n_roots; i++) {
219         if (subwin != roots[i])
220             continue;
221         win = Find_Child_At_Pointer(dpy, subwin);
222         if (win == XCB_WINDOW_NONE) {
223             free (roots);
224             return subwin;      /* No child - Return virtual root. */
225         }
226         subwin = win;
227         break;
228     }
229     free (roots);
230 
231     if (atom_wm_state == XCB_ATOM_NONE) {
232         atom_wm_state = Get_Atom(dpy, "WM_STATE");
233         if (atom_wm_state == XCB_ATOM_NONE)
234             return subwin;
235     }
236 
237     /* Check if subwin has WM_STATE */
238     if (Window_Has_Property(dpy, subwin, atom_wm_state))
239         return subwin;
240 
241     /* Attempt to find a client window in subwin's children */
242     win = Find_Client_In_Children(dpy, subwin);
243     if (win != XCB_WINDOW_NONE)
244         return win;             /* Found a client */
245 
246     /* Did not find a client */
247     return subwin;
248 }
249