29 #define G_LOG_DOMAIN "Dialogs.Window"
43 #include <xcb/xcb_ewmh.h>
44 #include <xcb/xcb_icccm.h>
45 #include <xcb/xcb_atom.h>
64 #define CLIENTSTATE 10
65 #define CLIENTWINDOWTYPE 10
76 WIN_MATCH_FIELD_TITLE,
77 WIN_MATCH_FIELD_CLASS,
80 WIN_MATCH_FIELD_DESKTOP,
82 } WinModeMatchingFields;
84 static WinModeField matching_window_fields[WIN_MATCH_NUM_FIELDS] = {
85 { .field_name =
"title", .enabled = TRUE, },
86 { .field_name =
"class", .enabled = TRUE, },
87 { .field_name =
"role", .enabled = TRUE, },
88 { .field_name =
"name", .enabled = TRUE, },
89 { .field_name =
"desktop", .enabled = TRUE, }
92 static gboolean window_matching_fields_parsed = FALSE;
98 xcb_get_window_attributes_reply_t xattr;
104 xcb_atom_t state[CLIENTSTATE];
106 xcb_atom_t window_type[CLIENTWINDOWTYPE];
112 cairo_surface_t *
icon;
113 gboolean icon_checked;
114 uint32_t icon_fetch_uid;
132 unsigned int wmdn_len;
133 unsigned int clf_len;
134 unsigned int name_len;
135 unsigned int title_len;
136 unsigned int role_len;
137 GRegex *window_regex;
138 } ModeModePrivateData;
140 winlist *cache_client = NULL;
147 static winlist* winlist_new ()
149 winlist *l = g_malloc (
sizeof ( winlist ) );
151 l->array = g_malloc_n ( WINLIST + 1,
sizeof ( xcb_window_t ) );
152 l->data = g_malloc_n ( WINLIST + 1,
sizeof ( client* ) );
165 static int winlist_append ( winlist *l, xcb_window_t w, client *d )
167 if ( l->len > 0 && !( l->len % WINLIST ) ) {
168 l->array = g_realloc ( l->array, sizeof ( xcb_window_t ) * ( l->len + WINLIST + 1 ) );
169 l->data = g_realloc ( l->data, sizeof ( client* ) * ( l->len + WINLIST + 1 ) );
173 if ( l->data == NULL || l->array == NULL ) {
178 l->array[l->len++] = w;
182 static void winlist_empty ( winlist *l )
184 while ( l->len > 0 ) {
185 client *c = l->data[--l->len];
188 cairo_surface_destroy ( c->icon );
194 g_free ( c->wmdesktopstr );
205 static void winlist_free ( winlist *l )
223 static int winlist_find ( winlist *l, xcb_window_t w )
229 for ( i = ( l->len - 1 ); i >= 0; i-- ) {
230 if ( l->array[i] == w ) {
240 static void x11_cache_create (
void )
242 if ( cache_client == NULL ) {
243 cache_client = winlist_new ();
250 static void x11_cache_free (
void )
252 winlist_free ( cache_client );
265 static xcb_get_window_attributes_reply_t * window_get_attributes ( xcb_window_t w )
267 xcb_get_window_attributes_cookie_t c = xcb_get_window_attributes (
xcb->
connection, w );
268 xcb_get_window_attributes_reply_t *r = xcb_get_window_attributes_reply (
xcb->
connection, c, NULL );
275 static int client_has_state ( client *c, xcb_atom_t state )
277 for (
int i = 0; i < c->states; i++ ) {
278 if ( c->state[i] == state ) {
285 static int client_has_window_type ( client *c, xcb_atom_t type )
287 for (
int i = 0; i < c->window_types; i++ ) {
288 if ( c->window_type[i] == type ) {
296 static client* window_client ( ModeModePrivateData *pd, xcb_window_t win )
298 if ( win == XCB_WINDOW_NONE ) {
302 int idx = winlist_find ( cache_client, win );
305 return cache_client->data[idx];
309 xcb_get_window_attributes_reply_t *attr = window_get_attributes ( win );
314 client *c = g_malloc0 (
sizeof ( client ) );
318 memmove ( &c->xattr, attr, sizeof ( xcb_get_window_attributes_reply_t ) );
320 xcb_get_property_cookie_t cky = xcb_ewmh_get_wm_state ( &
xcb->
ewmh, win );
321 xcb_ewmh_get_atoms_reply_t states;
322 if ( xcb_ewmh_get_wm_state_reply ( &
xcb->
ewmh, cky, &states, NULL ) ) {
323 c->states = MIN ( CLIENTSTATE, states.atoms_len );
324 memcpy ( c->state, states.atoms, MIN ( CLIENTSTATE, states.atoms_len ) * sizeof ( xcb_atom_t ) );
325 xcb_ewmh_get_atoms_reply_wipe ( &states );
327 cky = xcb_ewmh_get_wm_window_type ( &
xcb->
ewmh, win );
328 if ( xcb_ewmh_get_wm_window_type_reply ( &
xcb->
ewmh, cky, &states, NULL ) ) {
329 c->window_types = MIN ( CLIENTWINDOWTYPE, states.atoms_len );
330 memcpy ( c->window_type, states.atoms, MIN ( CLIENTWINDOWTYPE, states.atoms_len ) * sizeof ( xcb_atom_t ) );
331 xcb_ewmh_get_atoms_reply_wipe ( &states );
335 if ( c->title == NULL ) {
338 pd->title_len = MAX ( c->title ? g_utf8_strlen ( c->title, -1 ) : 0, pd->title_len );
341 pd->role_len = MAX ( c->role ? g_utf8_strlen ( c->role, -1 ) : 0, pd->role_len );
344 xcb_icccm_get_wm_class_reply_t wcr;
345 if ( xcb_icccm_get_wm_class_reply (
xcb->
connection, cky, &wcr, NULL ) ) {
348 pd->name_len = MAX ( c->name ? g_utf8_strlen ( c->name, -1 ) : 0, pd->name_len );
349 xcb_icccm_get_wm_class_reply_wipe ( &wcr );
352 xcb_get_property_cookie_t cc = xcb_icccm_get_wm_hints (
xcb->
connection, c->window );
353 xcb_icccm_wm_hints_t r;
354 if ( xcb_icccm_get_wm_hints_reply (
xcb->
connection, cc, &r, NULL ) ) {
355 c->hint_flags = r.flags;
358 winlist_append ( cache_client, c->window, c );
366 const winlist *ids = ( winlist * ) rmpd->ids;
368 int idx = winlist_find ( cache_client, ids->array[index] );
369 g_assert ( idx >= 0 );
370 client *c = cache_client->data[idx];
373 for (
int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) {
380 if ( c->title != NULL && c->title[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_TITLE].enabled ) {
384 if ( test == tokens[j]->invert && c->class != NULL && c->class[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_CLASS].enabled ) {
388 if ( test == tokens[j]->invert && c->role != NULL && c->role[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_ROLE].enabled ) {
392 if ( test == tokens[j]->invert && c->name != NULL && c->name[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_NAME].enabled ) {
395 if ( test == tokens[j]->invert && c->wmdesktopstr != NULL && c->wmdesktopstr[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_DESKTOP].enabled ) {
408 static void window_mode_parse_fields ()
410 window_matching_fields_parsed = TRUE;
414 const char *
const sep =
",#";
416 for (
unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) {
417 matching_window_fields[i].enabled = FALSE;
419 for (
char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL;
420 token = strtok_r ( NULL, sep, &savept ) ) {
421 if ( strcmp ( token,
"all" ) == 0 ) {
422 for (
unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) {
423 matching_window_fields[i].enabled = TRUE;
428 gboolean matched = FALSE;
429 for (
unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) {
430 const char * field_name = matching_window_fields[i].field_name;
431 if ( strcmp ( token, field_name ) == 0 ) {
432 matching_window_fields[i].enabled = TRUE;
437 g_warning (
"Invalid window field name :%s", token );
442 g_free ( switcher_str );
445 static unsigned int window_mode_get_num_entries (
const Mode *sw )
449 return pd->ids ? pd->ids->len : 0;
455 static const char * _window_name_list_entry (
const char *str, uint32_t length,
int entry )
459 while ( index < entry && offset < length ) {
460 if ( str[offset] == 0 ) {
467 static void _window_mode_load_data (
Mode *sw,
unsigned int cd )
472 xcb_window_t wins[100];
473 xcb_window_t curr_win_id;
479 if ( !xcb_ewmh_get_active_window_reply ( &
xcb->
ewmh, c, &curr_win_id, NULL ) ) {
484 unsigned int current_desktop = 0;
486 if ( !xcb_ewmh_get_current_desktop_reply ( &
xcb->
ewmh, c, ¤t_desktop, NULL ) ) {
490 c = xcb_ewmh_get_client_list_stacking ( &
xcb->
ewmh, 0 );
491 xcb_ewmh_get_windows_reply_t clients;
492 if ( xcb_ewmh_get_client_list_stacking_reply ( &
xcb->
ewmh, c, &clients, NULL ) ) {
493 nwins = MIN ( 100, clients.windows_len );
494 memcpy ( wins, clients.windows, nwins * sizeof ( xcb_window_t ) );
495 xcb_ewmh_get_windows_reply_wipe ( &clients );
499 if ( xcb_ewmh_get_client_list_reply ( &
xcb->
ewmh, c, &clients, NULL ) ) {
500 nwins = MIN ( 100, clients.windows_len );
501 memcpy ( wins, clients.windows, nwins * sizeof ( xcb_window_t ) );
502 xcb_ewmh_get_windows_reply_wipe ( &clients );
509 pd->ids = winlist_new ();
512 xcb_ewmh_get_utf8_strings_reply_t names;
513 int has_names = FALSE;
514 if ( xcb_ewmh_get_desktop_names_reply ( &
xcb->
ewmh, c, &names, NULL ) ) {
518 for ( i = nwins - 1; i > -1; i-- ) {
519 client *c = window_client ( pd, wins[i] );
521 && !c->xattr.override_redirect
522 && !client_has_window_type ( c,
xcb->
ewmh._NET_WM_WINDOW_TYPE_DOCK )
523 && !client_has_window_type ( c,
xcb->
ewmh._NET_WM_WINDOW_TYPE_DESKTOP )
524 && !client_has_state ( c,
xcb->
ewmh._NET_WM_STATE_SKIP_PAGER )
525 && !client_has_state ( c,
xcb->
ewmh._NET_WM_STATE_SKIP_TASKBAR ) ) {
526 pd->clf_len = MAX ( pd->clf_len, ( c->class != NULL ) ? ( g_utf8_strlen ( c->class, -1 ) ) : 0 );
528 if ( client_has_state ( c,
xcb->
ewmh._NET_WM_STATE_DEMANDS_ATTENTION ) ) {
531 if ( ( c->hint_flags & XCB_ICCCM_WM_HINT_X_URGENCY ) != 0 ) {
535 if ( c->window == curr_win_id ) {
539 xcb_get_property_cookie_t cookie;
540 xcb_get_property_reply_t *r;
542 c->wmdesktop = 0xFFFFFFFF;
544 xcb_get_property (
xcb->
connection, 0, c->window,
xcb->
ewmh._NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 0,
546 r = xcb_get_property_reply (
xcb->
connection, cookie, NULL );
548 if ( r->type == XCB_ATOM_CARDINAL ) {
549 c->wmdesktop = *( (uint32_t *) xcb_get_property_value ( r ) );
553 if ( c->wmdesktop != 0xFFFFFFFF ) {
557 if ( pango_parse_markup ( _window_name_list_entry ( names.strings, names.strings_len,
558 c->wmdesktop ), -1, 0, NULL, &output, NULL, NULL ) ) {
559 c->wmdesktopstr = output;
562 c->wmdesktopstr = g_strdup (
"Invalid name" );
566 c->wmdesktopstr = g_strdup ( _window_name_list_entry ( names.strings, names.strings_len, c->wmdesktop ) );
570 c->wmdesktopstr = g_strdup_printf (
"%u", (uint32_t) c->wmdesktop );
574 c->wmdesktopstr = g_strdup (
"" );
576 pd->wmdn_len = MAX ( pd->wmdn_len, g_utf8_strlen ( c->wmdesktopstr, -1 ) );
577 if ( cd && c->wmdesktop != current_desktop ) {
580 winlist_append ( pd->ids, c->window, NULL );
585 xcb_ewmh_get_utf8_strings_reply_wipe ( &names );
589 static int window_mode_init (
Mode *sw )
592 ModeModePrivateData *pd = g_malloc0 (
sizeof ( *pd ) );
593 pd->window_regex = g_regex_new (
"{[-\\w]+(:-?[0-9]+)?}", 0, 0, NULL );
595 _window_mode_load_data ( sw, FALSE );
596 if ( !window_matching_fields_parsed ) {
597 window_mode_parse_fields ();
602 static int window_mode_init_cd (
Mode *sw )
605 ModeModePrivateData *pd = g_malloc0 (
sizeof ( *pd ) );
606 pd->window_regex = g_regex_new (
"{[-\\w]+(:-?[0-9]+)?}", 0, 0, NULL );
608 _window_mode_load_data ( sw, TRUE );
609 if ( !window_matching_fields_parsed ) {
610 window_mode_parse_fields ();
616 static inline int act_on_window ( xcb_window_t window )
621 char window_regex[100];
623 g_snprintf ( window_regex,
sizeof window_regex,
"%d", window );
627 GError *error = NULL;
628 g_spawn_async ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error );
629 if ( error != NULL ) {
630 char *msg = g_strdup_printf (
"Failed to execute action for window: '%s'\nError: '%s'", window_regex, error->message );
634 g_error_free ( error );
643 static ModeMode window_mode_result (
Mode *sw,
int mretv, G_GNUC_UNUSED
char **input,
644 unsigned int selected_line )
657 else if ( ( mretv & (
MENU_OK ) ) ) {
659 act_on_window ( rmpd->ids->array[selected_line] );
665 uint32_t wmdesktop = 0;
666 xcb_get_property_cookie_t cookie;
667 xcb_get_property_reply_t *r;
669 unsigned int current_desktop = 0;
671 if ( !xcb_ewmh_get_current_desktop_reply ( &
xcb->
ewmh, c, ¤t_desktop, NULL ) ) {
675 cookie = xcb_get_property (
xcb->
connection, 0, rmpd->ids->array[selected_line],
676 xcb->
ewmh._NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 0, 1 );
677 r = xcb_get_property_reply (
xcb->
connection, cookie, NULL );
678 if ( r && r->type == XCB_ATOM_CARDINAL ) {
679 wmdesktop = *( (uint32_t *) xcb_get_property_value ( r ) );
681 if ( r && r->type != XCB_ATOM_CARDINAL ) {
683 wmdesktop = current_desktop;
688 if ( wmdesktop != current_desktop ) {
689 xcb_ewmh_request_change_current_desktop ( &
xcb->
ewmh,
694 xcb_ewmh_request_change_active_window ( &
xcb->
ewmh,
xcb->
screen_nbr, rmpd->ids->array[selected_line],
695 XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER,
701 xcb_ewmh_request_close_window ( &(
xcb->
ewmh ),
xcb->
screen_nbr, rmpd->ids->array[selected_line], XCB_CURRENT_TIME, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER );
707 static void window_mode_destroy (
Mode *sw )
710 if ( rmpd != NULL ) {
711 winlist_free ( rmpd->ids );
713 g_free ( rmpd->cache );
714 g_regex_unref ( rmpd->window_regex );
721 const ModeModePrivateData *pd;
725 static void helper_eval_add_str ( GString *str,
const char *input,
int l,
int max_len )
728 const char *input_nn = input ? input :
"";
730 int nc = g_utf8_strlen ( input_nn, -1 );
733 spaces = MAX ( 0, max_len - nc );
734 g_string_append ( str, input_nn );
738 int bl = g_utf8_offset_to_pointer ( input_nn, l ) - input_nn;
739 g_string_append_len ( str, input_nn, bl );
743 g_string_append ( str, input_nn );
747 g_string_append_c ( str,
' ' );
750 static gboolean helper_eval_cb (
const GMatchInfo *info, GString *str, gpointer data )
752 struct arg *d = (
struct arg *) data;
755 match = g_match_info_fetch ( info, 0 );
756 if ( match != NULL ) {
758 if ( match[2] ==
':' ) {
759 l = (int) g_ascii_strtoll ( &match[3], NULL, 10 );
767 if ( match[1] ==
'w' ) {
768 helper_eval_add_str ( str, d->c->wmdesktopstr, l, d->pd->wmdn_len );
770 else if ( match[1] ==
'c' ) {
771 helper_eval_add_str ( str, d->c->class, l, d->pd->clf_len );
773 else if ( match[1] ==
't' ) {
774 helper_eval_add_str ( str, d->c->title, l, d->pd->title_len );
776 else if ( match[1] ==
'n' ) {
777 helper_eval_add_str ( str, d->c->name, l, d->pd->name_len );
779 else if ( match[1] ==
'r' ) {
780 helper_eval_add_str ( str, d->c->role, l, d->pd->role_len );
786 static char * _generate_display_string (
const ModeModePrivateData *pd, client *c )
788 struct arg d = { pd, c };
790 helper_eval_cb, &d, NULL );
791 return g_strchomp ( res );
794 static char *
_get_display_value (
const Mode *sw,
unsigned int selected_line,
int *state, G_GNUC_UNUSED GList **list,
int get_entry )
797 client *c = window_client ( rmpd, rmpd->ids->array[selected_line] );
799 return get_entry ? g_strdup (
"Window has fanished" ) : NULL;
807 return get_entry ? _generate_display_string ( rmpd, c ) : NULL;
813 static cairo_user_data_key_t data_key;
820 static cairo_surface_t * draw_surface_from_data (
int width,
int height, uint32_t *data )
822 unsigned long int len = width * height;
824 uint32_t *buffer = g_new0 ( uint32_t, len );
825 cairo_surface_t *surface;
828 for ( i = 0; i < len; i++ ) {
829 uint8_t a = ( data[i] >> 24 ) & 0xff;
830 double alpha = a / 255.0;
831 uint8_t r = ( ( data[i] >> 16 ) & 0xff ) * alpha;
832 uint8_t g = ( ( data[i] >> 8 ) & 0xff ) * alpha;
833 uint8_t b = ( ( data[i] >> 0 ) & 0xff ) * alpha;
834 buffer[i] = ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b;
837 surface = cairo_image_surface_create_for_data ( (
unsigned char *) buffer,
843 cairo_surface_set_user_data ( surface, &data_key, buffer, g_free );
847 static cairo_surface_t * ewmh_window_icon_from_reply ( xcb_get_property_reply_t *r, uint32_t preferred_size )
849 uint32_t *data, *end, *found_data = 0;
850 uint32_t found_size = 0;
852 if ( !r || r->type != XCB_ATOM_CARDINAL || r->format != 32 || r->length < 2 ) {
856 data = (uint32_t *) xcb_get_property_value ( r );
861 end = data + r->length;
867 while ( data + 1 < end ) {
869 uint64_t data_size = (uint64_t) data[0] * data[1];
870 if ( data_size > (uint64_t) ( end - data - 2 ) ) {
875 uint32_t size = MAX ( data[0], data[1] );
878 gboolean found_icon_too_small = found_size < preferred_size;
879 gboolean found_icon_too_large = found_size > preferred_size;
880 gboolean icon_empty = data[0] == 0 || data[1] == 0;
881 gboolean better_because_bigger = found_icon_too_small && size > found_size;
882 gboolean better_because_smaller = found_icon_too_large &&
883 size >= preferred_size && size < found_size;
884 if ( !icon_empty && ( better_because_bigger || better_because_smaller || found_size == 0 ) ) {
889 data += data_size + 2;
896 return draw_surface_from_data ( found_data[0], found_data[1], found_data + 2 );
899 static cairo_surface_t * get_net_wm_icon ( xcb_window_t xid, uint32_t preferred_size )
901 xcb_get_property_cookie_t cookie = xcb_get_property_unchecked (
903 xcb->
ewmh._NET_WM_ICON, XCB_ATOM_CARDINAL, 0, UINT32_MAX );
904 xcb_get_property_reply_t *r = xcb_get_property_reply (
xcb->
connection, cookie, NULL );
905 cairo_surface_t *surface = ewmh_window_icon_from_reply ( r, preferred_size );
909 static cairo_surface_t *_get_icon (
const Mode *sw,
unsigned int selected_line,
int size )
912 client *c = window_client ( rmpd, rmpd->ids->array[selected_line] );
913 if ( c->icon_checked == FALSE ) {
914 c->icon = get_net_wm_icon ( rmpd->ids->array[selected_line], size );
915 c->icon_checked = TRUE;
917 if ( c->icon == NULL && c->class ) {
918 if ( c->icon_fetch_uid > 0 ) {
931 .cfg_name_key =
"display-window",
932 ._init = window_mode_init,
933 ._get_num_entries = window_mode_get_num_entries,
934 ._result = window_mode_result,
935 ._destroy = window_mode_destroy,
936 ._token_match = window_match,
938 ._get_icon = _get_icon,
939 ._get_completion = NULL,
940 ._preprocess_input = NULL,
941 .private_data = NULL,
944 Mode window_mode_cd =
947 .cfg_name_key =
"display-windowcd",
948 ._init = window_mode_init_cd,
949 ._get_num_entries = window_mode_get_num_entries,
950 ._result = window_mode_result,
951 ._destroy = window_mode_destroy,
952 ._token_match = window_match,
954 ._get_icon = _get_icon,
955 ._get_completion = NULL,
956 ._preprocess_input = NULL,
957 .private_data = NULL,
961 #endif // WINDOW_MODE