1 /*
2 * rofi
3 *
4 * MIT/X11 License
5 * Copyright © 2012 Sean Pringle <sean.pringle@gmail.com>
6 * Copyright © 2013-2020 Qball Cow <qball@gmpclient.org>
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining
9 * a copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be
17 * included in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 */
28
29 /** Log domain for this module */
30 #define G_LOG_DOMAIN "X11Helper"
31
32 #include <config.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <stdint.h>
38 #include <glib.h>
39 #include <cairo.h>
40 #include <cairo-xcb.h>
41
42 #include <xcb/xcb.h>
43 #include <xcb/xcb_aux.h>
44 #include <xcb/randr.h>
45 #include <xcb/xinerama.h>
46 #include <xcb/xcb_ewmh.h>
47 #include <xcb/xproto.h>
48 #include <xcb/xkb.h>
49 #include <xkbcommon/xkbcommon.h>
50 #include <xkbcommon/xkbcommon-x11.h>
51 /** Indicate that we know the startup notification api is not yet stable. */
52 #define SN_API_NOT_YET_FROZEN
53 /** This function is declared as sn_launcher_context_set_application_id but implemented as sn_launcher_set_application_id. Quick Fix. */
54 #define sn_launcher_context_set_application_id sn_launcher_set_application_id
55 #include "rofi-types.h"
56 #include <libsn/sn.h>
57 #include "display.h"
58 #include "xcb-internal.h"
59 #include "xcb.h"
60 #include "settings.h"
61 #include "helper.h"
62 #include "timings.h"
63
64 #include <rofi.h>
65
66 /** Minimal randr preferred for running rofi (1.5) (Major version number) */
67 #define RANDR_PREF_MAJOR_VERSION 1
68 /** Minimal randr preferred for running rofi (1.5) (Minor version number) */
69 #define RANDR_PREF_MINOR_VERSION 5
70
71 /** Checks if the if x and y is inside rectangle. */
72 #define INTERSECT( x, y, x1, y1, w1, h1 ) ( ( ( ( x ) >= ( x1 ) ) && ( ( x ) < ( x1 + w1 ) ) ) && ( ( ( y ) >= ( y1 ) ) && ( ( y ) < ( y1 + h1 ) ) ) )
73 WindowManagerQuirk current_window_manager = WM_EWHM;
74
75 /**
76 * Structure holding xcb objects needed to function.
77 */
78 struct _xcb_stuff xcb_int = {
79 .connection = NULL,
80 .screen = NULL,
81 .screen_nbr = -1,
82 .sndisplay = NULL,
83 .sncontext = NULL,
84 .monitors = NULL
85 };
86 xcb_stuff *xcb = &xcb_int;
87
88 /**
89 * Depth of root window.
90 */
91 xcb_depth_t *depth = NULL;
92 xcb_visualtype_t *visual = NULL;
93 xcb_colormap_t map = XCB_COLORMAP_NONE;
94 /**
95 * Visual of the root window.
96 */
97 static xcb_visualtype_t *root_visual = NULL;
98 xcb_atom_t netatoms[NUM_NETATOMS];
99 const char *netatom_names[] = { EWMH_ATOMS ( ATOM_CHAR ) };
100
lookup_visual(xcb_screen_t * s,xcb_visualid_t visual)101 static xcb_visualtype_t * lookup_visual ( xcb_screen_t *s, xcb_visualid_t visual )
102 {
103 xcb_depth_iterator_t d;
104 d = xcb_screen_allowed_depths_iterator ( s );
105 for (; d.rem; xcb_depth_next ( &d ) ) {
106 xcb_visualtype_iterator_t v = xcb_depth_visuals_iterator ( d.data );
107 for (; v.rem; xcb_visualtype_next ( &v ) ) {
108 if ( v.data->visual_id == visual ) {
109 return v.data;
110 }
111 }
112 }
113 return 0;
114 }
115
x11_helper_get_screenshot_surface_window(xcb_window_t window,int size)116 cairo_surface_t *x11_helper_get_screenshot_surface_window ( xcb_window_t window, int size )
117 {
118 xcb_get_geometry_cookie_t cookie;
119 xcb_get_geometry_reply_t *reply;
120
121 cookie = xcb_get_geometry ( xcb->connection, window );
122 reply = xcb_get_geometry_reply ( xcb->connection, cookie, NULL );
123 if ( reply == NULL ) {
124 return NULL;
125 }
126
127 xcb_get_window_attributes_cookie_t attributesCookie = xcb_get_window_attributes ( xcb->connection, window );
128 xcb_get_window_attributes_reply_t *attributes = xcb_get_window_attributes_reply ( xcb->connection,
129 attributesCookie,
130 NULL );
131 if ( attributes == NULL || ( attributes->map_state != XCB_MAP_STATE_VIEWABLE ) ) {
132 free ( reply );
133 if ( attributes ) {
134 free ( attributes );
135 }
136 return NULL;
137 }
138 // Create a cairo surface for the window.
139 xcb_visualtype_t * vt = lookup_visual ( xcb->screen, attributes->visual );
140 free ( attributes );
141
142 cairo_surface_t *t = cairo_xcb_surface_create ( xcb->connection, window, vt, reply->width, reply->height );
143
144 if ( cairo_surface_status ( t ) != CAIRO_STATUS_SUCCESS ) {
145 cairo_surface_destroy ( t );
146 free ( reply );
147 return NULL;
148 }
149
150 // Scale the image, as we don't want to keep large one around.
151 int max = MAX ( reply->width, reply->height );
152 double scale = (double) size / max;
153
154 cairo_surface_t *s2 = cairo_surface_create_similar_image ( t, CAIRO_FORMAT_ARGB32, reply->width * scale, reply->height * scale );
155 free ( reply );
156
157 if ( cairo_surface_status ( s2 ) != CAIRO_STATUS_SUCCESS ) {
158 cairo_surface_destroy ( t );
159 return NULL;
160 }
161 // Paint it in.
162 cairo_t *d = cairo_create ( s2 );
163 cairo_scale ( d, scale, scale );
164 cairo_set_source_surface ( d, t, 0, 0 );
165 cairo_paint ( d );
166 cairo_destroy ( d );
167
168 cairo_surface_destroy ( t );
169 return s2;
170 }
171 /**
172 * Holds for each supported modifier the possible modifier mask.
173 * Check x11_mod_masks[MODIFIER]&mask != 0 to see if MODIFIER is activated.
174 */
x11_helper_get_screenshot_surface(void)175 cairo_surface_t *x11_helper_get_screenshot_surface ( void )
176 {
177 return cairo_xcb_surface_create ( xcb->connection,
178 xcb_stuff_get_root_window (), root_visual,
179 xcb->screen->width_in_pixels, xcb->screen->height_in_pixels );
180 }
181
get_root_pixmap(xcb_connection_t * c,xcb_screen_t * screen,xcb_atom_t atom)182 static xcb_pixmap_t get_root_pixmap ( xcb_connection_t *c,
183 xcb_screen_t *screen,
184 xcb_atom_t atom )
185 {
186 xcb_get_property_cookie_t cookie;
187 xcb_get_property_reply_t *reply;
188 xcb_pixmap_t rootpixmap = XCB_NONE;
189
190 cookie = xcb_get_property ( c,
191 0,
192 screen->root,
193 atom,
194 XCB_ATOM_PIXMAP,
195 0,
196 1 );
197
198 reply = xcb_get_property_reply ( c, cookie, NULL );
199
200 if ( reply ) {
201 if ( xcb_get_property_value_length ( reply ) == sizeof ( xcb_pixmap_t ) ) {
202 memcpy ( &rootpixmap, xcb_get_property_value ( reply ), sizeof ( xcb_pixmap_t ) );
203 }
204 free ( reply );
205 }
206
207 return rootpixmap;
208 }
209
x11_helper_get_bg_surface(void)210 cairo_surface_t * x11_helper_get_bg_surface ( void )
211 {
212 xcb_pixmap_t pm = get_root_pixmap ( xcb->connection, xcb->screen, netatoms[ESETROOT_PMAP_ID] );
213 if ( pm == XCB_NONE ) {
214 return NULL;
215 }
216 return cairo_xcb_surface_create ( xcb->connection, pm, root_visual,
217 xcb->screen->width_in_pixels, xcb->screen->height_in_pixels );
218 }
219
220 // retrieve a text property from a window
221 // technically we could use window_get_prop(), but this is better for character set support
window_get_text_prop(xcb_window_t w,xcb_atom_t atom)222 char* window_get_text_prop ( xcb_window_t w, xcb_atom_t atom )
223 {
224 xcb_get_property_cookie_t c = xcb_get_property ( xcb->connection, 0, w, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX );
225 xcb_get_property_reply_t *r = xcb_get_property_reply ( xcb->connection, c, NULL );
226 if ( r ) {
227 if ( xcb_get_property_value_length ( r ) > 0 ) {
228 char *str = NULL;
229 if ( r->type == netatoms[UTF8_STRING] ) {
230 str = g_strndup ( xcb_get_property_value ( r ), xcb_get_property_value_length ( r ) );
231 }
232 else if ( r->type == netatoms[STRING] ) {
233 str = rofi_latin_to_utf8_strdup ( xcb_get_property_value ( r ), xcb_get_property_value_length ( r ) );
234 }
235 else {
236 str = g_strdup ( "Invalid encoding." );
237 }
238
239 free ( r );
240 return str;
241 }
242 free ( r );
243 }
244 return NULL;
245 }
246
window_set_atom_prop(xcb_window_t w,xcb_atom_t prop,xcb_atom_t * atoms,int count)247 void window_set_atom_prop ( xcb_window_t w, xcb_atom_t prop, xcb_atom_t *atoms, int count )
248 {
249 xcb_change_property ( xcb->connection, XCB_PROP_MODE_REPLACE, w, prop, XCB_ATOM_ATOM, 32, count, atoms );
250 }
251
252 /****
253 * Code used to get monitor layout.
254 */
255
256 /**
257 * Free monitor structure.
258 */
x11_monitor_free(workarea * m)259 static void x11_monitor_free ( workarea *m )
260 {
261 g_free ( m->name );
262 g_free ( m );
263 }
264
x11_monitors_free(void)265 static void x11_monitors_free ( void )
266 {
267 while ( xcb->monitors != NULL ) {
268 workarea *m = xcb->monitors;
269 xcb->monitors = m->next;
270 x11_monitor_free ( m );
271 }
272 }
273
274 /**
275 * Create monitor based on output id
276 */
x11_get_monitor_from_output(xcb_randr_output_t out)277 static workarea * x11_get_monitor_from_output ( xcb_randr_output_t out )
278 {
279 xcb_randr_get_output_info_reply_t *op_reply;
280 xcb_randr_get_crtc_info_reply_t *crtc_reply;
281 xcb_randr_get_output_info_cookie_t it = xcb_randr_get_output_info ( xcb->connection, out, XCB_CURRENT_TIME );
282 op_reply = xcb_randr_get_output_info_reply ( xcb->connection, it, NULL );
283 if ( op_reply->crtc == XCB_NONE ) {
284 free ( op_reply );
285 return NULL;
286 }
287 xcb_randr_get_crtc_info_cookie_t ct = xcb_randr_get_crtc_info ( xcb->connection, op_reply->crtc, XCB_CURRENT_TIME );
288 crtc_reply = xcb_randr_get_crtc_info_reply ( xcb->connection, ct, NULL );
289 if ( !crtc_reply ) {
290 free ( op_reply );
291 return NULL;
292 }
293 workarea *retv = g_malloc0 ( sizeof ( workarea ) );
294 retv->x = crtc_reply->x;
295 retv->y = crtc_reply->y;
296 retv->w = crtc_reply->width;
297 retv->h = crtc_reply->height;
298
299 retv->mw = op_reply->mm_width;
300 retv->mh = op_reply->mm_height;
301
302 char *tname = (char *) xcb_randr_get_output_info_name ( op_reply );
303 int tname_len = xcb_randr_get_output_info_name_length ( op_reply );
304
305 retv->name = g_malloc0 ( ( tname_len + 1 ) * sizeof ( char ) );
306 memcpy ( retv->name, tname, tname_len );
307
308 free ( crtc_reply );
309 free ( op_reply );
310 return retv;
311 }
312
313 #if ( ( ( XCB_RANDR_MAJOR_VERSION >= RANDR_PREF_MAJOR_VERSION ) && ( XCB_RANDR_MINOR_VERSION >= RANDR_PREF_MINOR_VERSION ) ) \
314 || XCB_RANDR_MAJOR_VERSION > RANDR_PREF_MAJOR_VERSION )
315 /**
316 * @param mon The randr monitor to parse.
317 *
318 * Create monitor based on xrandr monitor id.
319 *
320 * @returns A workarea representing the monitor mon
321 */
x11_get_monitor_from_randr_monitor(xcb_randr_monitor_info_t * mon)322 static workarea *x11_get_monitor_from_randr_monitor ( xcb_randr_monitor_info_t *mon )
323 {
324 // Query to the name of the monitor.
325 xcb_generic_error_t *err;
326 xcb_get_atom_name_cookie_t anc = xcb_get_atom_name ( xcb->connection, mon->name );
327 xcb_get_atom_name_reply_t *atom_reply = xcb_get_atom_name_reply ( xcb->connection, anc, &err );
328 if ( err != NULL ) {
329 g_warning ( "Could not get RandR monitor name: X11 error code %d\n", err->error_code );
330 free ( err );
331 return NULL;
332 }
333 workarea *retv = g_malloc0 ( sizeof ( workarea ) );
334
335 // Is primary monitor.
336 retv->primary = mon->primary;
337
338 // Position and size.
339 retv->x = mon->x;
340 retv->y = mon->y;
341 retv->w = mon->width;
342 retv->h = mon->height;
343
344 // Physical
345 retv->mw = mon->width_in_millimeters;
346 retv->mh = mon->height_in_millimeters;
347
348 // Name
349 retv->name = g_strdup_printf ( "%.*s", xcb_get_atom_name_name_length ( atom_reply ), xcb_get_atom_name_name ( atom_reply ) );
350
351 // Free name atom.
352 free ( atom_reply );
353
354 return retv;
355 }
356 #endif
357
x11_is_extension_present(const char * extension)358 static int x11_is_extension_present ( const char *extension )
359 {
360 xcb_query_extension_cookie_t randr_cookie = xcb_query_extension ( xcb->connection, strlen ( extension ), extension );
361
362 xcb_query_extension_reply_t *randr_reply = xcb_query_extension_reply ( xcb->connection, randr_cookie, NULL );
363
364 int present = randr_reply->present;
365
366 free ( randr_reply );
367
368 return present;
369 }
370
x11_build_monitor_layout_xinerama()371 static void x11_build_monitor_layout_xinerama ()
372 {
373 xcb_xinerama_query_screens_cookie_t screens_cookie = xcb_xinerama_query_screens_unchecked (
374 xcb->connection
375 );
376
377 xcb_xinerama_query_screens_reply_t *screens_reply = xcb_xinerama_query_screens_reply (
378 xcb->connection,
379 screens_cookie,
380 NULL
381 );
382
383 xcb_xinerama_screen_info_iterator_t screens_iterator = xcb_xinerama_query_screens_screen_info_iterator (
384 screens_reply
385 );
386
387 for (; screens_iterator.rem > 0; xcb_xinerama_screen_info_next ( &screens_iterator ) ) {
388 workarea *w = g_malloc0 ( sizeof ( workarea ) );
389
390 w->x = screens_iterator.data->x_org;
391 w->y = screens_iterator.data->y_org;
392 w->w = screens_iterator.data->width;
393 w->h = screens_iterator.data->height;
394
395 w->next = xcb->monitors;
396 xcb->monitors = w;
397 }
398
399 int index = 0;
400 for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
401 iter->monitor_id = index++;
402 }
403
404 free ( screens_reply );
405 }
406
x11_build_monitor_layout()407 static void x11_build_monitor_layout ()
408 {
409 if ( xcb->monitors ) {
410 return;
411 }
412 // If RANDR is not available, try Xinerama
413 if ( !x11_is_extension_present ( "RANDR" ) ) {
414 // Check if xinerama is available.
415 if ( x11_is_extension_present ( "XINERAMA" ) ) {
416 g_debug ( "Query XINERAMA for monitor layout." );
417 x11_build_monitor_layout_xinerama ();
418 return;
419 }
420 g_debug ( "No RANDR or Xinerama available for getting monitor layout." );
421 return;
422 }
423 g_debug ( "Query RANDR for monitor layout." );
424
425 g_debug ( "Randr XCB api version: %d.%d.", XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION );
426 #if ( ( ( XCB_RANDR_MAJOR_VERSION == RANDR_PREF_MAJOR_VERSION ) && ( XCB_RANDR_MINOR_VERSION >= RANDR_PREF_MINOR_VERSION ) ) \
427 || XCB_RANDR_MAJOR_VERSION > RANDR_PREF_MAJOR_VERSION )
428 xcb_randr_query_version_cookie_t cversion = xcb_randr_query_version ( xcb->connection,
429 RANDR_PREF_MAJOR_VERSION, RANDR_PREF_MINOR_VERSION );
430 xcb_randr_query_version_reply_t *rversion = xcb_randr_query_version_reply ( xcb->connection, cversion, NULL );
431 if ( rversion ) {
432 g_debug ( "Found randr version: %d.%d", rversion->major_version, rversion->minor_version );
433 // Check if we are 1.5 and up.
434 if ( ( ( rversion->major_version == RANDR_PREF_MAJOR_VERSION ) && ( rversion->minor_version >= RANDR_PREF_MINOR_VERSION ) ) ||
435 ( rversion->major_version > RANDR_PREF_MAJOR_VERSION ) ) {
436 xcb_randr_get_monitors_cookie_t t = xcb_randr_get_monitors ( xcb->connection, xcb->screen->root, 1 );
437 xcb_randr_get_monitors_reply_t *mreply = xcb_randr_get_monitors_reply ( xcb->connection, t, NULL );
438 if ( mreply ) {
439 xcb_randr_monitor_info_iterator_t iter = xcb_randr_get_monitors_monitors_iterator ( mreply );
440 while ( iter.rem > 0 ) {
441 workarea *w = x11_get_monitor_from_randr_monitor ( iter.data );
442 if ( w ) {
443 w->next = xcb->monitors;
444 xcb->monitors = w;
445 }
446 xcb_randr_monitor_info_next ( &iter );
447 }
448 free ( mreply );
449 }
450 }
451 free ( rversion );
452 }
453 #endif
454
455 // If no monitors found.
456 if ( xcb->monitors == NULL ) {
457 xcb_randr_get_screen_resources_current_reply_t *res_reply;
458 xcb_randr_get_screen_resources_current_cookie_t src;
459 src = xcb_randr_get_screen_resources_current ( xcb->connection, xcb->screen->root );
460 res_reply = xcb_randr_get_screen_resources_current_reply ( xcb->connection, src, NULL );
461 if ( !res_reply ) {
462 return; //just report error
463 }
464 int mon_num = xcb_randr_get_screen_resources_current_outputs_length ( res_reply );
465 xcb_randr_output_t *ops = xcb_randr_get_screen_resources_current_outputs ( res_reply );
466
467 // Get primary.
468 xcb_randr_get_output_primary_cookie_t pc = xcb_randr_get_output_primary ( xcb->connection, xcb->screen->root );
469 xcb_randr_get_output_primary_reply_t *pc_rep = xcb_randr_get_output_primary_reply ( xcb->connection, pc, NULL );
470
471 for ( int i = mon_num - 1; i >= 0; i-- ) {
472 workarea *w = x11_get_monitor_from_output ( ops[i] );
473 if ( w ) {
474 w->next = xcb->monitors;
475 xcb->monitors = w;
476 if ( pc_rep && pc_rep->output == ops[i] ) {
477 w->primary = TRUE;
478 }
479 }
480 }
481 // If exists, free primary output reply.
482 if ( pc_rep ) {
483 free ( pc_rep );
484 }
485 free ( res_reply );
486 }
487
488 // Number monitor
489 int index = 0;
490 for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
491 iter->monitor_id = index++;
492 }
493 }
494
display_dump_monitor_layout(void)495 void display_dump_monitor_layout ( void )
496 {
497 int is_term = isatty ( fileno ( stdout ) );
498 printf ( "Monitor layout:\n" );
499 for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
500 printf ( "%s ID%s: %d", ( is_term ) ? color_bold : "", is_term ? color_reset : "", iter->monitor_id );
501 if ( iter->primary ) {
502 printf ( " (primary)" );
503 }
504 printf ( "\n" );
505 printf ( "%s name%s: %s\n", ( is_term ) ? color_bold : "", is_term ? color_reset : "", iter->name );
506 printf ( "%s position%s: %d,%d\n", ( is_term ) ? color_bold : "", is_term ? color_reset : "", iter->x, iter->y );
507 printf ( "%s size%s: %d,%d\n", ( is_term ) ? color_bold : "", is_term ? color_reset : "", iter->w, iter->h );
508 if ( iter->mw > 0 && iter->mh > 0 ) {
509 printf ( "%s size%s: %dmm,%dmm dpi: %.0f,%.0f\n",
510 ( is_term ) ? color_bold : "",
511 is_term ? color_reset : "",
512 iter->mw,
513 iter->mh,
514 iter->w * 25.4 / (double) iter->mw,
515 iter->h * 25.4 / (double) iter->mh
516 );
517 }
518 printf ( "\n" );
519 }
520 }
521
display_startup_notification(RofiHelperExecuteContext * context,GSpawnChildSetupFunc * child_setup,gpointer * user_data)522 void display_startup_notification ( RofiHelperExecuteContext *context, GSpawnChildSetupFunc *child_setup, gpointer *user_data )
523 {
524 if ( context == NULL ) {
525 return;
526 }
527
528 SnLauncherContext *sncontext;
529
530 sncontext = sn_launcher_context_new ( xcb->sndisplay, xcb->screen_nbr );
531
532 sn_launcher_context_set_name ( sncontext, context->name );
533 sn_launcher_context_set_description ( sncontext, context->description );
534 if ( context->binary != NULL ) {
535 sn_launcher_context_set_binary_name ( sncontext, context->binary );
536 }
537 if ( context->icon != NULL ) {
538 sn_launcher_context_set_icon_name ( sncontext, context->icon );
539 }
540 if ( context->app_id != NULL ) {
541 sn_launcher_context_set_application_id ( sncontext, context->app_id );
542 }
543 if ( context->wmclass != NULL ) {
544 sn_launcher_context_set_wmclass ( sncontext, context->wmclass );
545 }
546
547 xcb_get_property_cookie_t c;
548 unsigned int current_desktop = 0;
549
550 c = xcb_ewmh_get_current_desktop ( &xcb->ewmh, xcb->screen_nbr );
551 if ( xcb_ewmh_get_current_desktop_reply ( &xcb->ewmh, c, ¤t_desktop, NULL ) ) {
552 sn_launcher_context_set_workspace ( sncontext, current_desktop );
553 }
554
555 sn_launcher_context_initiate ( sncontext, "rofi", context->command, xcb->last_timestamp );
556
557 *child_setup = (GSpawnChildSetupFunc) sn_launcher_context_setup_child_process;
558 *user_data = sncontext;
559 }
560
monitor_get_dimension(int monitor_id,workarea * mon)561 static int monitor_get_dimension ( int monitor_id, workarea *mon )
562 {
563 memset ( mon, 0, sizeof ( workarea ) );
564 mon->w = xcb->screen->width_in_pixels;
565 mon->h = xcb->screen->height_in_pixels;
566
567 workarea *iter = NULL;
568 for ( iter = xcb->monitors; iter; iter = iter->next ) {
569 if ( iter->monitor_id == monitor_id ) {
570 *mon = *iter;
571 return TRUE;
572 }
573 }
574 return FALSE;
575 }
576 // find the dimensions of the monitor displaying point x,y
monitor_dimensions(int x,int y,workarea * mon)577 static void monitor_dimensions ( int x, int y, workarea *mon )
578 {
579 memset ( mon, 0, sizeof ( workarea ) );
580 mon->w = xcb->screen->width_in_pixels;
581 mon->h = xcb->screen->height_in_pixels;
582
583 for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
584 if ( INTERSECT ( x, y, iter->x, iter->y, iter->w, iter->h ) ) {
585 *mon = *iter;
586 break;
587 }
588 }
589 }
590
591 /**
592 * @param root The X11 window used to find the pointer position. Usually the root window.
593 * @param x The x position of the mouse [out]
594 * @param y The y position of the mouse [out]
595 *
596 * find mouse pointer location
597 *
598 * @returns TRUE when found, FALSE otherwise
599 */
pointer_get(xcb_window_t root,int * x,int * y)600 static int pointer_get ( xcb_window_t root, int *x, int *y )
601 {
602 *x = 0;
603 *y = 0;
604 xcb_query_pointer_cookie_t c = xcb_query_pointer ( xcb->connection, root );
605 xcb_query_pointer_reply_t *r = xcb_query_pointer_reply ( xcb->connection, c, NULL );
606 if ( r ) {
607 *x = r->root_x;
608 *y = r->root_y;
609 free ( r );
610 return TRUE;
611 }
612
613 return FALSE;
614 }
monitor_active_from_winid(xcb_drawable_t id,workarea * mon)615 static int monitor_active_from_winid ( xcb_drawable_t id, workarea *mon )
616 {
617 xcb_window_t root = xcb->screen->root;
618 xcb_get_geometry_cookie_t c = xcb_get_geometry ( xcb->connection, id );
619 xcb_get_geometry_reply_t *r = xcb_get_geometry_reply ( xcb->connection, c, NULL );
620 if ( r ) {
621 xcb_translate_coordinates_cookie_t ct = xcb_translate_coordinates ( xcb->connection, id, root, r->x, r->y );
622 xcb_translate_coordinates_reply_t *t = xcb_translate_coordinates_reply ( xcb->connection, ct, NULL );
623 if ( t ) {
624 // place the menu above the window
625 // if some window is focused, place menu above window, else fall
626 // back to selected monitor.
627 mon->x = t->dst_x - r->x;
628 mon->y = t->dst_y - r->y;
629 mon->w = r->width;
630 mon->h = r->height;
631 free ( r );
632 free ( t );
633 return TRUE;
634 }
635 free ( r );
636 }
637 return FALSE;
638 }
monitor_active_from_id_focused(int mon_id,workarea * mon)639 static int monitor_active_from_id_focused ( int mon_id, workarea *mon )
640 {
641 int retv = FALSE;
642 xcb_window_t active_window;
643 xcb_get_property_cookie_t awc;
644 awc = xcb_ewmh_get_active_window ( &xcb->ewmh, xcb->screen_nbr );
645 if ( !xcb_ewmh_get_active_window_reply ( &xcb->ewmh, awc, &active_window, NULL ) ) {
646 g_debug ( "Failed to get active window, falling back to mouse location (-5)." );
647 return retv;
648 }
649 xcb_query_tree_cookie_t tree_cookie = xcb_query_tree ( xcb->connection, active_window );
650 xcb_query_tree_reply_t *tree_reply = xcb_query_tree_reply ( xcb->connection, tree_cookie, NULL );
651 if ( !tree_reply ) {
652 g_debug ( "Failed to get parent window, falling back to mouse location (-5)." );
653 return retv;
654 }
655 // get geometry.
656 xcb_get_geometry_cookie_t c = xcb_get_geometry ( xcb->connection, active_window );
657 xcb_get_geometry_reply_t *r = xcb_get_geometry_reply ( xcb->connection, c, NULL );
658 if ( !r ) {
659 g_debug ( "Failed to get geometry of active window, falling back to mouse location (-5)." );
660 free ( tree_reply );
661 return retv;
662 }
663 xcb_translate_coordinates_cookie_t ct = xcb_translate_coordinates ( xcb->connection, tree_reply->parent, r->root, r->x, r->y );
664 xcb_translate_coordinates_reply_t *t = xcb_translate_coordinates_reply ( xcb->connection, ct, NULL );
665 if ( t ) {
666 if ( mon_id == -2 ) {
667 // place the menu above the window
668 // if some window is focused, place menu above window, else fall
669 // back to selected monitor.
670 mon->x = t->dst_x - r->x;
671 mon->y = t->dst_y - r->y;
672 mon->w = r->width;
673 mon->h = r->height;
674 retv = TRUE;
675 }
676 else if ( mon_id == -4 ) {
677 monitor_dimensions ( t->dst_x, t->dst_y, mon );
678 retv = TRUE;
679 }
680 free ( t );
681 }
682 else {
683 g_debug ( "Failed to get translate position of active window, falling back to mouse location (-5)." );
684 }
685 free ( r );
686 free ( tree_reply );
687 return retv;
688 }
monitor_active_from_id(int mon_id,workarea * mon)689 static int monitor_active_from_id ( int mon_id, workarea *mon )
690 {
691 xcb_window_t root = xcb->screen->root;
692 int x, y;
693 // At mouse position.
694 if ( mon_id == -3 ) {
695 if ( pointer_get ( root, &x, &y ) ) {
696 monitor_dimensions ( x, y, mon );
697 mon->x = x;
698 mon->y = y;
699 return TRUE;
700 }
701 }
702 // Focused monitor
703 else if ( mon_id == -1 ) {
704 // Get the current desktop.
705 unsigned int current_desktop = 0;
706 xcb_get_property_cookie_t gcdc;
707 gcdc = xcb_ewmh_get_current_desktop ( &xcb->ewmh, xcb->screen_nbr );
708 if ( xcb_ewmh_get_current_desktop_reply ( &xcb->ewmh, gcdc, ¤t_desktop, NULL ) ) {
709 xcb_get_property_cookie_t c = xcb_ewmh_get_desktop_viewport ( &xcb->ewmh, xcb->screen_nbr );
710 xcb_ewmh_get_desktop_viewport_reply_t vp;
711 if ( xcb_ewmh_get_desktop_viewport_reply ( &xcb->ewmh, c, &vp, NULL ) ) {
712 if ( current_desktop < vp.desktop_viewport_len ) {
713 monitor_dimensions ( vp.desktop_viewport[current_desktop].x,
714 vp.desktop_viewport[current_desktop].y, mon );
715 xcb_ewmh_get_desktop_viewport_reply_wipe ( &vp );
716 return TRUE;
717 }
718 else {
719 g_debug ( "Viewport does not exist for current desktop: %d, falling back to mouse location (-5)", current_desktop );
720 }
721 xcb_ewmh_get_desktop_viewport_reply_wipe ( &vp );
722 }
723 else {
724 g_debug ( "Failed to get viewport for current desktop: %d, falling back to mouse location (-5).", current_desktop );
725 }
726 }
727 else {
728 g_debug ( "Failed to get current desktop, falling back to mouse location (-5)." );
729 }
730 }
731 else if ( mon_id == -2 || mon_id == -4 ) {
732 if ( monitor_active_from_id_focused ( mon_id, mon ) ) {
733 return TRUE;
734 }
735 }
736 // Monitor that has mouse pointer.
737 else if ( mon_id == -5 ) {
738 if ( pointer_get ( root, &x, &y ) ) {
739 monitor_dimensions ( x, y, mon );
740 return TRUE;
741 }
742 // This is our give up point.
743 return FALSE;
744 }
745 g_debug ( "Failed to find monitor, fall back to monitor showing mouse." );
746 return monitor_active_from_id ( -5, mon );
747 }
748
749 // determine which monitor holds the active window, or failing that the mouse pointer
monitor_active(workarea * mon)750 int monitor_active ( workarea *mon )
751 {
752 if ( config.monitor != NULL ) {
753 for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
754 if ( g_strcmp0 ( config.monitor, iter->name ) == 0 ) {
755 *mon = *iter;
756 return TRUE;
757 }
758 }
759 }
760 // Grab primary.
761 if ( g_strcmp0 ( config.monitor, "primary" ) == 0 ) {
762 for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
763 if ( iter->primary ) {
764 *mon = *iter;
765 return TRUE;
766 }
767 }
768 }
769 if ( g_str_has_prefix ( config.monitor, "wid:" ) ) {
770 char *end = NULL;
771 xcb_drawable_t win = g_ascii_strtoll ( config.monitor + 4, &end, 0 );
772 if ( end != config.monitor ) {
773 if ( monitor_active_from_winid ( win, mon ) ) {
774 return TRUE;
775 }
776 }
777 }
778 {
779 // IF fail, fall back to classic mode.
780 char *end = NULL;
781 gint64 mon_id = g_ascii_strtoll ( config.monitor, &end, 0 );
782 if ( end != config.monitor ) {
783 if ( mon_id >= 0 ) {
784 if ( monitor_get_dimension ( mon_id, mon ) ) {
785 return TRUE;
786 }
787 g_warning ( "Failed to find selected monitor." );
788 }
789 else {
790 return monitor_active_from_id ( mon_id, mon );
791 }
792 }
793 }
794 // Fallback.
795 monitor_dimensions ( 0, 0, mon );
796 return FALSE;
797 }
798
799 /**
800 * @param state Internal state of the menu.
801 * @param xse X selection event.
802 *
803 * Handle paste event.
804 */
rofi_view_paste(RofiViewState * state,xcb_selection_notify_event_t * xse)805 static void rofi_view_paste ( RofiViewState *state, xcb_selection_notify_event_t *xse )
806 {
807 if ( xse->property == XCB_ATOM_NONE ) {
808 g_warning ( "Failed to convert selection" );
809 }
810 else if ( xse->property == xcb->ewmh.UTF8_STRING ) {
811 gchar *text = window_get_text_prop ( xse->requestor, xcb->ewmh.UTF8_STRING );
812 if ( text != NULL && text[0] != '\0' ) {
813 unsigned int dl = strlen ( text );
814 // Strip new line
815 for ( unsigned int i = 0; i < dl; i++ ) {
816 if ( text[i] == '\n' ) {
817 text[i] = '\0';
818 }
819 }
820 rofi_view_handle_text ( state, text );
821 }
822 g_free ( text );
823 }
824 else {
825 g_warning ( "Failed" );
826 }
827 }
828
x11_button_to_nk_bindings_button(guint32 x11_button,NkBindingsMouseButton * button)829 static gboolean x11_button_to_nk_bindings_button ( guint32 x11_button, NkBindingsMouseButton *button )
830 {
831 switch ( x11_button )
832 {
833 case 1:
834 *button = NK_BINDINGS_MOUSE_BUTTON_PRIMARY;
835 break;
836 case 3:
837 *button = NK_BINDINGS_MOUSE_BUTTON_SECONDARY;
838 break;
839 case 2:
840 *button = NK_BINDINGS_MOUSE_BUTTON_MIDDLE;
841 break;
842 case 8:
843 *button = NK_BINDINGS_MOUSE_BUTTON_BACK;
844 break;
845 case 9:
846 *button = NK_BINDINGS_MOUSE_BUTTON_FORWARD;
847 break;
848 case 4:
849 case 5:
850 case 6:
851 case 7:
852 return FALSE;
853 default:
854 *button = NK_BINDINGS_MOUSE_BUTTON_EXTRA + x11_button;
855 }
856 return TRUE;
857 }
858
x11_button_to_nk_bindings_scroll(guint32 x11_button,NkBindingsScrollAxis * axis,gint32 * steps)859 static gboolean x11_button_to_nk_bindings_scroll ( guint32 x11_button, NkBindingsScrollAxis *axis, gint32 *steps )
860 {
861 *steps = 1;
862 switch ( x11_button )
863 {
864 case 4:
865 *steps = -1;
866 /* fallthrough */
867 case 5:
868 *axis = NK_BINDINGS_SCROLL_AXIS_VERTICAL;
869 break;
870 case 6:
871 *steps = -1;
872 /* fallthrough */
873 case 7:
874 *axis = NK_BINDINGS_SCROLL_AXIS_HORIZONTAL;
875 break;
876 default:
877 return FALSE;
878 }
879 return TRUE;
880 }
881
882 /**
883 * Process X11 events in the main-loop (gui-thread) of the application.
884 */
main_loop_x11_event_handler_view(xcb_generic_event_t * event)885 static void main_loop_x11_event_handler_view ( xcb_generic_event_t *event )
886 {
887 RofiViewState *state = rofi_view_get_active ();
888 if ( state == NULL ) {
889 return;
890 }
891
892 switch ( event->response_type & ~0x80 )
893 {
894 case XCB_EXPOSE:
895 rofi_view_frame_callback ();
896 break;
897 case XCB_CONFIGURE_NOTIFY:
898 {
899 xcb_configure_notify_event_t *xce = (xcb_configure_notify_event_t *) event;
900 rofi_view_temp_configure_notify ( state, xce );
901 break;
902 }
903 case XCB_MOTION_NOTIFY:
904 {
905 if ( config.click_to_exit == TRUE ) {
906 xcb->mouse_seen = TRUE;
907 }
908 xcb_motion_notify_event_t *xme = (xcb_motion_notify_event_t *) event;
909 rofi_view_handle_mouse_motion ( state, xme->event_x, xme->event_y );
910 break;
911 }
912 case XCB_BUTTON_PRESS:
913 {
914 xcb_button_press_event_t *bpe = (xcb_button_press_event_t *) event;
915 NkBindingsMouseButton button;
916 NkBindingsScrollAxis axis;
917 gint32 steps;
918
919 xcb->last_timestamp = bpe->time;
920 rofi_view_handle_mouse_motion ( state, bpe->event_x, bpe->event_y );
921 if ( x11_button_to_nk_bindings_button ( bpe->detail, &button ) ) {
922 nk_bindings_seat_handle_button ( xcb->bindings_seat, NULL, button, NK_BINDINGS_BUTTON_STATE_PRESS, bpe->time );
923 }
924 else if ( x11_button_to_nk_bindings_scroll ( bpe->detail, &axis, &steps ) ) {
925 nk_bindings_seat_handle_scroll ( xcb->bindings_seat, NULL, axis, steps );
926 }
927 break;
928 }
929 case XCB_BUTTON_RELEASE:
930 {
931 xcb_button_release_event_t *bre = (xcb_button_release_event_t *) event;
932 NkBindingsMouseButton button;
933
934 xcb->last_timestamp = bre->time;
935 if ( x11_button_to_nk_bindings_button ( bre->detail, &button ) ) {
936 nk_bindings_seat_handle_button ( xcb->bindings_seat, NULL, button, NK_BINDINGS_BUTTON_STATE_RELEASE, bre->time );
937 }
938 if ( config.click_to_exit == TRUE ) {
939 if ( !xcb->mouse_seen ) {
940 rofi_view_temp_click_to_exit ( state, bre->event );
941 }
942 xcb->mouse_seen = FALSE;
943 }
944 break;
945 }
946 // Paste event.
947 case XCB_SELECTION_NOTIFY:
948 rofi_view_paste ( state, (xcb_selection_notify_event_t *) event );
949 break;
950 case XCB_KEYMAP_NOTIFY:
951 {
952 xcb_keymap_notify_event_t *kne = (xcb_keymap_notify_event_t *) event;
953 for ( gint32 by = 0; by < 31; ++by ) {
954 for ( gint8 bi = 0; bi < 7; ++bi ) {
955 if ( kne->keys[by] & ( 1 << bi ) ) {
956 // X11 keycodes starts at 8
957 nk_bindings_seat_handle_key ( xcb->bindings_seat, NULL, ( 8 * by + bi ) + 8, NK_BINDINGS_KEY_STATE_PRESSED );
958 }
959 }
960 }
961 break;
962 }
963 case XCB_KEY_PRESS:
964 {
965 xcb_key_press_event_t *xkpe = (xcb_key_press_event_t *) event;
966 gchar *text;
967
968 xcb->last_timestamp = xkpe->time;
969 text = nk_bindings_seat_handle_key_with_modmask ( xcb->bindings_seat, NULL, xkpe->state, xkpe->detail, NK_BINDINGS_KEY_STATE_PRESS );
970 if ( text != NULL ) {
971 rofi_view_handle_text ( state, text );
972 }
973 break;
974 }
975 case XCB_KEY_RELEASE:
976 {
977 xcb_key_release_event_t *xkre = (xcb_key_release_event_t *) event;
978 xcb->last_timestamp = xkre->time;
979 nk_bindings_seat_handle_key ( xcb->bindings_seat, NULL, xkre->detail, NK_BINDINGS_KEY_STATE_RELEASE );
980 break;
981 }
982 default:
983 break;
984 }
985 rofi_view_maybe_update ( state );
986 }
987
main_loop_x11_event_handler(xcb_generic_event_t * ev,G_GNUC_UNUSED gpointer user_data)988 static gboolean main_loop_x11_event_handler ( xcb_generic_event_t *ev, G_GNUC_UNUSED gpointer user_data )
989 {
990 if ( ev == NULL ) {
991 int status = xcb_connection_has_error ( xcb->connection );
992 if ( status > 0 ) {
993 g_warning ( "The XCB connection to X server had a fatal error: %d", status );
994 g_main_loop_quit ( xcb->main_loop );
995 return G_SOURCE_REMOVE;
996 }
997 else {
998 g_warning ( "main_loop_x11_event_handler: ev == NULL, status == %d", status );
999 return G_SOURCE_CONTINUE;
1000 }
1001 }
1002 uint8_t type = ev->response_type & ~0x80;
1003 if ( type == xcb->xkb.first_event ) {
1004 switch ( ev->pad0 )
1005 {
1006 case XCB_XKB_MAP_NOTIFY:
1007 {
1008 struct xkb_keymap *keymap = xkb_x11_keymap_new_from_device ( nk_bindings_seat_get_context ( xcb->bindings_seat ), xcb->connection, xcb->xkb.device_id, 0 );
1009 struct xkb_state *state = xkb_x11_state_new_from_device ( keymap, xcb->connection, xcb->xkb.device_id );
1010 nk_bindings_seat_update_keymap ( xcb->bindings_seat, keymap, state );
1011 xkb_keymap_unref ( keymap );
1012 xkb_state_unref ( state );
1013 break;
1014 }
1015 case XCB_XKB_STATE_NOTIFY:
1016 {
1017 xcb_xkb_state_notify_event_t *ksne = (xcb_xkb_state_notify_event_t *) ev;
1018 nk_bindings_seat_update_mask ( xcb->bindings_seat, NULL,
1019 ksne->baseMods,
1020 ksne->latchedMods,
1021 ksne->lockedMods,
1022 ksne->baseGroup,
1023 ksne->latchedGroup,
1024 ksne->lockedGroup );
1025 rofi_view_maybe_update ( rofi_view_get_active () );
1026 break;
1027 }
1028 }
1029 return G_SOURCE_CONTINUE;
1030 }
1031 if ( xcb->sndisplay != NULL ) {
1032 sn_xcb_display_process_event ( xcb->sndisplay, ev );
1033 }
1034 main_loop_x11_event_handler_view ( ev );
1035 return G_SOURCE_CONTINUE;
1036 }
1037
take_pointer(xcb_window_t w,int iters)1038 static int take_pointer ( xcb_window_t w, int iters )
1039 {
1040 int i = 0;
1041 while ( TRUE ) {
1042 if ( xcb_connection_has_error ( xcb->connection ) ) {
1043 g_warning ( "Connection has error" );
1044 exit ( EXIT_FAILURE );
1045 }
1046 xcb_grab_pointer_cookie_t cc = xcb_grab_pointer ( xcb->connection, 1, w, XCB_EVENT_MASK_BUTTON_RELEASE,
1047 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, w, XCB_NONE, XCB_CURRENT_TIME );
1048 xcb_grab_pointer_reply_t *r = xcb_grab_pointer_reply ( xcb->connection, cc, NULL );
1049 if ( r ) {
1050 if ( r->status == XCB_GRAB_STATUS_SUCCESS ) {
1051 free ( r );
1052 return 1;
1053 }
1054 free ( r );
1055 }
1056 if ( ( ++i ) > iters ) {
1057 break;
1058 }
1059 struct timespec del = {
1060 .tv_sec = 0,
1061 .tv_nsec = 1000000
1062 };
1063 nanosleep ( &del, NULL );
1064 }
1065 return 0;
1066 }
1067
take_keyboard(xcb_window_t w,int iters)1068 static int take_keyboard ( xcb_window_t w, int iters )
1069 {
1070 int i = 0;
1071 while ( TRUE ) {
1072 if ( xcb_connection_has_error ( xcb->connection ) ) {
1073 g_warning ( "Connection has error" );
1074 exit ( EXIT_FAILURE );
1075 }
1076 xcb_grab_keyboard_cookie_t cc = xcb_grab_keyboard ( xcb->connection,
1077 1, w, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC,
1078 XCB_GRAB_MODE_ASYNC );
1079 xcb_grab_keyboard_reply_t *r = xcb_grab_keyboard_reply ( xcb->connection, cc, NULL );
1080 if ( r ) {
1081 if ( r->status == XCB_GRAB_STATUS_SUCCESS ) {
1082 free ( r );
1083 return 1;
1084 }
1085 free ( r );
1086 }
1087 if ( ( ++i ) > iters ) {
1088 break;
1089 }
1090 struct timespec del = {
1091 .tv_sec = 0,
1092 .tv_nsec = 1000000
1093 };
1094 nanosleep ( &del, NULL );
1095 }
1096 return 0;
1097 }
1098
release_keyboard(void)1099 static void release_keyboard ( void )
1100 {
1101 xcb_ungrab_keyboard ( xcb->connection, XCB_CURRENT_TIME );
1102 }
release_pointer(void)1103 static void release_pointer ( void )
1104 {
1105 xcb_ungrab_pointer ( xcb->connection, XCB_CURRENT_TIME );
1106 }
1107
1108 /** X server error depth. to handle nested errors. */
1109 static int error_trap_depth = 0;
error_trap_push(G_GNUC_UNUSED SnDisplay * display,G_GNUC_UNUSED xcb_connection_t * xdisplay)1110 static void error_trap_push ( G_GNUC_UNUSED SnDisplay *display, G_GNUC_UNUSED xcb_connection_t *xdisplay )
1111 {
1112 ++error_trap_depth;
1113 }
1114
error_trap_pop(G_GNUC_UNUSED SnDisplay * display,xcb_connection_t * xdisplay)1115 static void error_trap_pop ( G_GNUC_UNUSED SnDisplay *display, xcb_connection_t *xdisplay )
1116 {
1117 if ( error_trap_depth == 0 ) {
1118 g_warning ( "Error trap underflow!" );
1119 exit ( EXIT_FAILURE );
1120 }
1121
1122 xcb_flush ( xdisplay );
1123 --error_trap_depth;
1124 }
1125
1126 /**
1127 * Fill in the list of frequently used X11 Atoms.
1128 */
x11_create_frequently_used_atoms(void)1129 static void x11_create_frequently_used_atoms ( void )
1130 {
1131 // X atom values
1132 for ( int i = 0; i < NUM_NETATOMS; i++ ) {
1133 xcb_intern_atom_cookie_t cc = xcb_intern_atom ( xcb->connection, 0, strlen ( netatom_names[i] ), netatom_names[i] );
1134 xcb_intern_atom_reply_t *r = xcb_intern_atom_reply ( xcb->connection, cc, NULL );
1135 if ( r ) {
1136 netatoms[i] = r->atom;
1137 free ( r );
1138 }
1139 }
1140 }
1141
x11_helper_discover_window_manager(void)1142 static void x11_helper_discover_window_manager ( void )
1143 {
1144 xcb_window_t wm_win = 0;
1145 xcb_get_property_cookie_t cc = xcb_ewmh_get_supporting_wm_check_unchecked ( &xcb->ewmh,
1146 xcb_stuff_get_root_window () );
1147
1148 if ( xcb_ewmh_get_supporting_wm_check_reply ( &xcb->ewmh, cc, &wm_win, NULL ) ) {
1149 xcb_ewmh_get_utf8_strings_reply_t wtitle;
1150 xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name_unchecked ( &( xcb->ewmh ), wm_win );
1151 if ( xcb_ewmh_get_wm_name_reply ( &( xcb->ewmh ), cookie, &wtitle, (void *) 0 ) ) {
1152 if ( wtitle.strings_len > 0 ) {
1153 g_debug ( "Found window manager: %s", wtitle.strings );
1154 if ( g_strcmp0 ( wtitle.strings, "i3" ) == 0 ) {
1155 current_window_manager = WM_DO_NOT_CHANGE_CURRENT_DESKTOP | WM_PANGO_WORKSPACE_NAMES;
1156 }
1157 }
1158 xcb_ewmh_get_utf8_strings_reply_wipe ( &wtitle );
1159 }
1160 }
1161 }
1162
display_setup(GMainLoop * main_loop,NkBindings * bindings)1163 gboolean display_setup ( GMainLoop *main_loop, NkBindings *bindings )
1164 {
1165 // Get DISPLAY, first env, then argument.
1166 // We never modify display_str content.
1167 char *display_str = ( char *) g_getenv ( "DISPLAY" );
1168 find_arg_str ( "-display", &display_str );
1169
1170 xcb->main_loop = main_loop;
1171 xcb->source = g_water_xcb_source_new ( g_main_loop_get_context ( xcb->main_loop ), display_str, &xcb->screen_nbr, main_loop_x11_event_handler, NULL, NULL );
1172 if ( xcb->source == NULL ) {
1173 g_warning ( "Failed to open display: %s", display_str );
1174 return FALSE;
1175 }
1176 xcb->connection = g_water_xcb_source_get_connection ( xcb->source );
1177
1178 TICK_N ( "Open Display" );
1179
1180 xcb->screen = xcb_aux_get_screen ( xcb->connection, xcb->screen_nbr );
1181
1182 x11_build_monitor_layout ();
1183
1184 xcb_intern_atom_cookie_t *ac = xcb_ewmh_init_atoms ( xcb->connection, &xcb->ewmh );
1185 xcb_generic_error_t *errors = NULL;
1186 xcb_ewmh_init_atoms_replies ( &xcb->ewmh, ac, &errors );
1187 if ( errors ) {
1188 g_warning ( "Failed to create EWMH atoms" );
1189 free ( errors );
1190 }
1191 // Discover the current active window manager.
1192 x11_helper_discover_window_manager ();
1193 TICK_N ( "Setup XCB" );
1194
1195 if ( xkb_x11_setup_xkb_extension ( xcb->connection, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
1196 XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, NULL, NULL, &xcb->xkb.first_event, NULL ) < 0 ) {
1197 g_warning ( "cannot setup XKB extension!" );
1198 return FALSE;
1199 }
1200
1201 xcb->xkb.device_id = xkb_x11_get_core_keyboard_device_id ( xcb->connection );
1202
1203 enum
1204 {
1205 required_events =
1206 ( XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
1207 XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
1208 XCB_XKB_EVENT_TYPE_STATE_NOTIFY ),
1209
1210 required_nkn_details =
1211 ( XCB_XKB_NKN_DETAIL_KEYCODES ),
1212
1213 required_map_parts =
1214 ( XCB_XKB_MAP_PART_KEY_TYPES |
1215 XCB_XKB_MAP_PART_KEY_SYMS |
1216 XCB_XKB_MAP_PART_MODIFIER_MAP |
1217 XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
1218 XCB_XKB_MAP_PART_KEY_ACTIONS |
1219 XCB_XKB_MAP_PART_VIRTUAL_MODS |
1220 XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP ),
1221
1222 required_state_details =
1223 ( XCB_XKB_STATE_PART_MODIFIER_BASE |
1224 XCB_XKB_STATE_PART_MODIFIER_LATCH |
1225 XCB_XKB_STATE_PART_MODIFIER_LOCK |
1226 XCB_XKB_STATE_PART_GROUP_BASE |
1227 XCB_XKB_STATE_PART_GROUP_LATCH |
1228 XCB_XKB_STATE_PART_GROUP_LOCK ),
1229 };
1230
1231 static const xcb_xkb_select_events_details_t details = {
1232 .affectNewKeyboard = required_nkn_details,
1233 .newKeyboardDetails = required_nkn_details,
1234 .affectState = required_state_details,
1235 .stateDetails = required_state_details,
1236 };
1237 xcb_xkb_select_events ( xcb->connection, xcb->xkb.device_id, required_events, /* affectWhich */
1238 0, /* clear */
1239 required_events, /* selectAll */
1240 required_map_parts, /* affectMap */
1241 required_map_parts, /* map */
1242 &details );
1243
1244 xcb->bindings_seat = nk_bindings_seat_new ( bindings, XKB_CONTEXT_NO_FLAGS );
1245 struct xkb_keymap *keymap = xkb_x11_keymap_new_from_device ( nk_bindings_seat_get_context ( xcb->bindings_seat ), xcb->connection, xcb->xkb.device_id, XKB_KEYMAP_COMPILE_NO_FLAGS );
1246 if ( keymap == NULL ) {
1247 g_warning ( "Failed to get Keymap for current keyboard device." );
1248 return FALSE;
1249 }
1250 struct xkb_state *state = xkb_x11_state_new_from_device ( keymap, xcb->connection, xcb->xkb.device_id );
1251 if ( state == NULL ) {
1252 g_warning ( "Failed to get state object for current keyboard device." );
1253 return FALSE;
1254 }
1255
1256 nk_bindings_seat_update_keymap ( xcb->bindings_seat, keymap, state );
1257 xkb_state_unref ( state );
1258 xkb_keymap_unref ( keymap );
1259
1260 // determine numlock mask so we can bind on keys with and without it
1261 x11_create_frequently_used_atoms ( );
1262
1263 if ( xcb_connection_has_error ( xcb->connection ) ) {
1264 g_warning ( "Connection has error" );
1265 return FALSE;
1266 }
1267
1268 // startup not.
1269 xcb->sndisplay = sn_xcb_display_new ( xcb->connection, error_trap_push, error_trap_pop );
1270 if ( xcb_connection_has_error ( xcb->connection ) ) {
1271 g_warning ( "Connection has error" );
1272 return FALSE;
1273 }
1274
1275 if ( xcb->sndisplay != NULL ) {
1276 xcb->sncontext = sn_launchee_context_new_from_environment ( xcb->sndisplay, xcb->screen_nbr );
1277 }
1278 if ( xcb_connection_has_error ( xcb->connection ) ) {
1279 g_warning ( "Connection has error" );
1280 return FALSE;
1281 }
1282
1283 return TRUE;
1284 }
1285
x11_create_visual_and_colormap(void)1286 static void x11_create_visual_and_colormap ( void )
1287 {
1288 xcb_depth_t *root_depth = NULL;
1289 xcb_depth_iterator_t depth_iter;
1290 for ( depth_iter = xcb_screen_allowed_depths_iterator ( xcb->screen ); depth_iter.rem; xcb_depth_next ( &depth_iter ) ) {
1291 xcb_depth_t *d = depth_iter.data;
1292
1293 xcb_visualtype_iterator_t visual_iter;
1294 for ( visual_iter = xcb_depth_visuals_iterator ( d ); visual_iter.rem; xcb_visualtype_next ( &visual_iter ) ) {
1295 xcb_visualtype_t *v = visual_iter.data;
1296 if ( ( v->bits_per_rgb_value == 8 ) && ( d->depth == 32 ) && ( v->_class == XCB_VISUAL_CLASS_TRUE_COLOR ) ) {
1297 depth = d;
1298 visual = v;
1299 }
1300 if ( xcb->screen->root_visual == v->visual_id ) {
1301 root_depth = d;
1302 root_visual = v;
1303 }
1304 }
1305 }
1306 if ( visual != NULL ) {
1307 xcb_void_cookie_t c;
1308 xcb_generic_error_t *e;
1309 map = xcb_generate_id ( xcb->connection );
1310 c = xcb_create_colormap_checked ( xcb->connection, XCB_COLORMAP_ALLOC_NONE, map, xcb->screen->root, visual->visual_id );
1311 e = xcb_request_check ( xcb->connection, c );
1312 if ( e ) {
1313 depth = NULL;
1314 visual = NULL;
1315 free ( e );
1316 }
1317 }
1318
1319 if ( visual == NULL ) {
1320 depth = root_depth;
1321 visual = root_visual;
1322 map = xcb->screen->default_colormap;
1323 }
1324 }
1325
1326 /** Retry count of grabbing keyboard. */
1327 unsigned int lazy_grab_retry_count_kb = 0;
1328 /** Retry count of grabbing pointer. */
1329 unsigned int lazy_grab_retry_count_pt = 0;
lazy_grab_pointer(G_GNUC_UNUSED gpointer data)1330 static gboolean lazy_grab_pointer ( G_GNUC_UNUSED gpointer data )
1331 {
1332 // After 5 sec.
1333 if ( lazy_grab_retry_count_pt > ( 5 * 1000 ) ) {
1334 g_warning ( "Failed to grab pointer after %u times. Giving up.", lazy_grab_retry_count_pt );
1335 return G_SOURCE_REMOVE;
1336 }
1337 if ( take_pointer ( xcb_stuff_get_root_window (), 0 ) ) {
1338 return G_SOURCE_REMOVE;
1339 }
1340 lazy_grab_retry_count_pt++;
1341 return G_SOURCE_CONTINUE;
1342 }
lazy_grab_keyboard(G_GNUC_UNUSED gpointer data)1343 static gboolean lazy_grab_keyboard ( G_GNUC_UNUSED gpointer data )
1344 {
1345 // After 5 sec.
1346 if ( lazy_grab_retry_count_kb > ( 5 * 1000 ) ) {
1347 g_warning ( "Failed to grab keyboard after %u times. Giving up.", lazy_grab_retry_count_kb );
1348 g_main_loop_quit ( xcb->main_loop );
1349 return G_SOURCE_REMOVE;
1350 }
1351 if ( take_keyboard ( xcb_stuff_get_root_window (), 0 ) ) {
1352 return G_SOURCE_REMOVE;
1353 }
1354 lazy_grab_retry_count_kb++;
1355 return G_SOURCE_CONTINUE;
1356 }
1357
display_late_setup(void)1358 gboolean display_late_setup ( void )
1359 {
1360 x11_create_visual_and_colormap ();
1361
1362 /**
1363 * Create window (without showing)
1364 */
1365 // Try to grab the keyboard as early as possible.
1366 // We grab this using the rootwindow (as dmenu does it).
1367 // this seems to result in the smallest delay for most people.
1368 if ( find_arg ( "-normal-window" ) >= 0 ) {
1369 return TRUE;
1370 }
1371 if ( find_arg ( "-no-lazy-grab" ) >= 0 ) {
1372 if ( !take_keyboard ( xcb_stuff_get_root_window (), 500 ) ) {
1373 g_warning ( "Failed to grab keyboard, even after %d uS.", 500 * 1000 );
1374 return FALSE;
1375 }
1376 if ( !take_pointer ( xcb_stuff_get_root_window (), 100 ) ) {
1377 g_warning ( "Failed to grab mouse pointer, even after %d uS.", 100 * 1000 );
1378 }
1379 }
1380 else {
1381 if ( !take_keyboard ( xcb_stuff_get_root_window (), 0 ) ) {
1382 g_timeout_add ( 1, lazy_grab_keyboard, NULL );
1383 }
1384 if ( !take_pointer ( xcb_stuff_get_root_window (), 0 ) ) {
1385 g_timeout_add ( 1, lazy_grab_pointer, NULL );
1386 }
1387 }
1388 return TRUE;
1389 }
1390
xcb_stuff_get_root_window(void)1391 xcb_window_t xcb_stuff_get_root_window ( void )
1392 {
1393 return xcb->screen->root;
1394 }
1395
display_early_cleanup(void)1396 void display_early_cleanup ( void )
1397 {
1398 release_keyboard ( );
1399 release_pointer ( );
1400 xcb_flush ( xcb->connection );
1401 }
1402
display_cleanup(void)1403 void display_cleanup ( void )
1404 {
1405 if ( xcb->connection == NULL ) {
1406 return;
1407 }
1408
1409 g_debug ( "Cleaning up XCB and XKB" );
1410
1411 nk_bindings_seat_free ( xcb->bindings_seat );
1412 if ( xcb->sncontext != NULL ) {
1413 sn_launchee_context_unref ( xcb->sncontext );
1414 xcb->sncontext = NULL;
1415 }
1416 if ( xcb->sndisplay != NULL ) {
1417 sn_display_unref ( xcb->sndisplay );
1418 xcb->sndisplay = NULL;
1419 }
1420 x11_monitors_free ();
1421 xcb_ewmh_connection_wipe ( &( xcb->ewmh ) );
1422 xcb_flush ( xcb->connection );
1423 xcb_aux_sync ( xcb->connection );
1424 g_water_xcb_source_free ( xcb->source );
1425 xcb->source = NULL;
1426 xcb->connection = NULL;
1427 xcb->screen = NULL;
1428 xcb->screen_nbr = 0;
1429 }
1430
x11_disable_decoration(xcb_window_t window)1431 void x11_disable_decoration ( xcb_window_t window )
1432 {
1433 // Flag used to indicate we are setting the decoration type.
1434 const uint32_t MWM_HINTS_DECORATIONS = ( 1 << 1 );
1435 // Motif property data structure
1436 struct MotifWMHints
1437 {
1438 uint32_t flags;
1439 uint32_t functions;
1440 uint32_t decorations;
1441 int32_t inputMode;
1442 uint32_t state;
1443 };
1444
1445 struct MotifWMHints hints;
1446 hints.flags = MWM_HINTS_DECORATIONS;
1447 hints.decorations = 0;
1448 hints.functions = 0;
1449 hints.inputMode = 0;
1450 hints.state = 0;
1451
1452 xcb_atom_t ha = netatoms[_MOTIF_WM_HINTS];
1453 xcb_change_property ( xcb->connection, XCB_PROP_MODE_REPLACE, window, ha, ha, 32, 5, &hints );
1454 }
1455