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;
115 gboolean thumbnail_checked;
133 unsigned int wmdn_len;
134 unsigned int clf_len;
135 unsigned int name_len;
136 unsigned int title_len;
137 unsigned int role_len;
138 GRegex *window_regex;
139 } ModeModePrivateData;
141 winlist *cache_client = NULL;
148 static winlist* winlist_new ()
150 winlist *l = g_malloc (
sizeof ( winlist ) );
152 l->array = g_malloc_n ( WINLIST + 1,
sizeof ( xcb_window_t ) );
153 l->data = g_malloc_n ( WINLIST + 1,
sizeof ( client* ) );
166 static int winlist_append ( winlist *l, xcb_window_t w, client *d )
168 if ( l->len > 0 && !( l->len % WINLIST ) ) {
169 l->array = g_realloc ( l->array, sizeof ( xcb_window_t ) * ( l->len + WINLIST + 1 ) );
170 l->data = g_realloc ( l->data, sizeof ( client* ) * ( l->len + WINLIST + 1 ) );
174 if ( l->data == NULL || l->array == NULL ) {
179 l->array[l->len++] = w;
183 static void winlist_empty ( winlist *l )
185 while ( l->len > 0 ) {
186 client *c = l->data[--l->len];
189 cairo_surface_destroy ( c->icon );
195 g_free ( c->wmdesktopstr );
206 static void winlist_free ( winlist *l )
224 static int winlist_find ( winlist *l, xcb_window_t w )
230 for ( i = ( l->len - 1 ); i >= 0; i-- ) {
231 if ( l->array[i] == w ) {
241 static void x11_cache_create (
void )
243 if ( cache_client == NULL ) {
244 cache_client = winlist_new ();
251 static void x11_cache_free (
void )
253 winlist_free ( cache_client );
266 static xcb_get_window_attributes_reply_t * window_get_attributes ( xcb_window_t w )
268 xcb_get_window_attributes_cookie_t c = xcb_get_window_attributes (
xcb->
connection, w );
269 xcb_get_window_attributes_reply_t *r = xcb_get_window_attributes_reply (
xcb->
connection, c, NULL );
276 static int client_has_state ( client *c, xcb_atom_t state )
278 for (
int i = 0; i < c->states; i++ ) {
279 if ( c->state[i] == state ) {
286 static int client_has_window_type ( client *c, xcb_atom_t type )
288 for (
int i = 0; i < c->window_types; i++ ) {
289 if ( c->window_type[i] == type ) {
297 static client* window_client ( ModeModePrivateData *pd, xcb_window_t win )
299 if ( win == XCB_WINDOW_NONE ) {
303 int idx = winlist_find ( cache_client, win );
306 return cache_client->data[idx];
310 xcb_get_window_attributes_reply_t *attr = window_get_attributes ( win );
315 client *c = g_malloc0 (
sizeof ( client ) );
319 memmove ( &c->xattr, attr, sizeof ( xcb_get_window_attributes_reply_t ) );
321 xcb_get_property_cookie_t cky = xcb_ewmh_get_wm_state ( &
xcb->
ewmh, win );
322 xcb_ewmh_get_atoms_reply_t states;
323 if ( xcb_ewmh_get_wm_state_reply ( &
xcb->
ewmh, cky, &states, NULL ) ) {
324 c->states = MIN ( CLIENTSTATE, states.atoms_len );
325 memcpy ( c->state, states.atoms, MIN ( CLIENTSTATE, states.atoms_len ) * sizeof ( xcb_atom_t ) );
326 xcb_ewmh_get_atoms_reply_wipe ( &states );
328 cky = xcb_ewmh_get_wm_window_type ( &
xcb->
ewmh, win );
329 if ( xcb_ewmh_get_wm_window_type_reply ( &
xcb->
ewmh, cky, &states, NULL ) ) {
330 c->window_types = MIN ( CLIENTWINDOWTYPE, states.atoms_len );
331 memcpy ( c->window_type, states.atoms, MIN ( CLIENTWINDOWTYPE, states.atoms_len ) * sizeof ( xcb_atom_t ) );
332 xcb_ewmh_get_atoms_reply_wipe ( &states );
336 if ( c->title == NULL ) {
339 pd->title_len = MAX ( c->title ? g_utf8_strlen ( c->title, -1 ) : 0, pd->title_len );
342 pd->role_len = MAX ( c->role ? g_utf8_strlen ( c->role, -1 ) : 0, pd->role_len );
345 xcb_icccm_get_wm_class_reply_t wcr;
346 if ( xcb_icccm_get_wm_class_reply (
xcb->
connection, cky, &wcr, NULL ) ) {
349 pd->name_len = MAX ( c->name ? g_utf8_strlen ( c->name, -1 ) : 0, pd->name_len );
350 xcb_icccm_get_wm_class_reply_wipe ( &wcr );
353 xcb_get_property_cookie_t cc = xcb_icccm_get_wm_hints (
xcb->
connection, c->window );
354 xcb_icccm_wm_hints_t r;
355 if ( xcb_icccm_get_wm_hints_reply (
xcb->
connection, cc, &r, NULL ) ) {
356 c->hint_flags = r.flags;
359 winlist_append ( cache_client, c->window, c );
367 const winlist *ids = ( winlist * ) rmpd->ids;
369 int idx = winlist_find ( cache_client, ids->array[index] );
370 g_assert ( idx >= 0 );
371 client *c = cache_client->data[idx];
374 for (
int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) {
381 if ( c->title != NULL && c->title[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_TITLE].enabled ) {
385 if ( test == tokens[j]->invert && c->class != NULL && c->class[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_CLASS].enabled ) {
389 if ( test == tokens[j]->invert && c->role != NULL && c->role[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_ROLE].enabled ) {
393 if ( test == tokens[j]->invert && c->name != NULL && c->name[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_NAME].enabled ) {
396 if ( test == tokens[j]->invert && c->wmdesktopstr != NULL && c->wmdesktopstr[0] !=
'\0' && matching_window_fields[WIN_MATCH_FIELD_DESKTOP].enabled ) {
409 static void window_mode_parse_fields ()
411 window_matching_fields_parsed = TRUE;
415 const char *
const sep =
",#";
417 for (
unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) {
418 matching_window_fields[i].enabled = FALSE;
420 for (
char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL;
421 token = strtok_r ( NULL, sep, &savept ) ) {
422 if ( strcmp ( token,
"all" ) == 0 ) {
423 for (
unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) {
424 matching_window_fields[i].enabled = TRUE;
429 gboolean matched = FALSE;
430 for (
unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) {
431 const char * field_name = matching_window_fields[i].field_name;
432 if ( strcmp ( token, field_name ) == 0 ) {
433 matching_window_fields[i].enabled = TRUE;
438 g_warning (
"Invalid window field name :%s", token );
443 g_free ( switcher_str );
446 static unsigned int window_mode_get_num_entries (
const Mode *sw )
450 return pd->ids ? pd->ids->len : 0;
456 const char *invalid_desktop_name =
"n/a";
457 static const char * _window_name_list_entry (
const char *str, uint32_t length,
int entry )
461 while ( index < entry && offset < length ) {
462 if ( str[offset] == 0 ) {
467 if ( offset >= length ) {
468 return invalid_desktop_name;
472 static void _window_mode_load_data (
Mode *sw,
unsigned int cd )
476 xcb_window_t curr_win_id;
483 if ( !xcb_ewmh_get_active_window_reply ( &
xcb->
ewmh, c, &curr_win_id, NULL ) ) {
488 unsigned int current_desktop = 0;
490 if ( !xcb_ewmh_get_current_desktop_reply ( &
xcb->
ewmh, c, ¤t_desktop, NULL ) ) {
494 c = xcb_ewmh_get_client_list_stacking ( &
xcb->
ewmh, 0 );
495 xcb_ewmh_get_windows_reply_t clients = { 0, };
496 if ( xcb_ewmh_get_client_list_stacking_reply ( &
xcb->
ewmh, c, &clients, NULL ) ) {
501 if ( xcb_ewmh_get_client_list_reply ( &
xcb->
ewmh, c, &clients, NULL ) ) {
509 if ( clients.windows_len > 0 ) {
513 pd->ids = winlist_new ();
516 xcb_ewmh_get_utf8_strings_reply_t names;
517 int has_names = FALSE;
518 if ( xcb_ewmh_get_desktop_names_reply ( &
xcb->
ewmh, c, &names, NULL ) ) {
522 for ( i = clients.windows_len - 1; i > -1; i-- ) {
523 client *c = window_client ( pd, clients.windows[i] );
525 && !c->xattr.override_redirect
526 && !client_has_window_type ( c,
xcb->
ewmh._NET_WM_WINDOW_TYPE_DOCK )
527 && !client_has_window_type ( c,
xcb->
ewmh._NET_WM_WINDOW_TYPE_DESKTOP )
528 && !client_has_state ( c,
xcb->
ewmh._NET_WM_STATE_SKIP_PAGER )
529 && !client_has_state ( c,
xcb->
ewmh._NET_WM_STATE_SKIP_TASKBAR ) ) {
530 pd->clf_len = MAX ( pd->clf_len, ( c->class != NULL ) ? ( g_utf8_strlen ( c->class, -1 ) ) : 0 );
532 if ( client_has_state ( c,
xcb->
ewmh._NET_WM_STATE_DEMANDS_ATTENTION ) ) {
535 if ( ( c->hint_flags & XCB_ICCCM_WM_HINT_X_URGENCY ) != 0 ) {
539 if ( c->window == curr_win_id ) {
543 xcb_get_property_cookie_t cookie;
544 xcb_get_property_reply_t *r;
546 c->wmdesktop = 0xFFFFFFFF;
548 xcb_get_property (
xcb->
connection, 0, c->window,
xcb->
ewmh._NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 0,
550 r = xcb_get_property_reply (
xcb->
connection, cookie, NULL );
552 if ( r->type == XCB_ATOM_CARDINAL ) {
553 c->wmdesktop = *( (uint32_t *) xcb_get_property_value ( r ) );
557 if ( c->wmdesktop != 0xFFFFFFFF ) {
561 if ( pango_parse_markup ( _window_name_list_entry ( names.strings, names.strings_len,
562 c->wmdesktop ), -1, 0, NULL, &output, NULL, NULL ) ) {
563 c->wmdesktopstr = output;
566 c->wmdesktopstr = g_strdup (
"Invalid name" );
570 c->wmdesktopstr = g_strdup ( _window_name_list_entry ( names.strings, names.strings_len, c->wmdesktop ) );
574 c->wmdesktopstr = g_strdup_printf (
"%u", (uint32_t) c->wmdesktop );
578 c->wmdesktopstr = g_strdup (
"" );
580 pd->wmdn_len = MAX ( pd->wmdn_len, g_utf8_strlen ( c->wmdesktopstr, -1 ) );
581 if ( cd && c->wmdesktop != current_desktop ) {
584 winlist_append ( pd->ids, c->window, NULL );
589 xcb_ewmh_get_utf8_strings_reply_wipe ( &names );
592 xcb_ewmh_get_windows_reply_wipe ( &clients );
594 static int window_mode_init (
Mode *sw )
597 ModeModePrivateData *pd = g_malloc0 (
sizeof ( *pd ) );
598 pd->window_regex = g_regex_new (
"{[-\\w]+(:-?[0-9]+)?}", 0, 0, NULL );
600 _window_mode_load_data ( sw, FALSE );
601 if ( !window_matching_fields_parsed ) {
602 window_mode_parse_fields ();
607 static int window_mode_init_cd (
Mode *sw )
610 ModeModePrivateData *pd = g_malloc0 (
sizeof ( *pd ) );
611 pd->window_regex = g_regex_new (
"{[-\\w]+(:-?[0-9]+)?}", 0, 0, NULL );
613 _window_mode_load_data ( sw, TRUE );
614 if ( !window_matching_fields_parsed ) {
615 window_mode_parse_fields ();
621 static inline int act_on_window ( xcb_window_t window )
626 char window_regex[100];
628 g_snprintf ( window_regex,
sizeof window_regex,
"%d", window );
632 GError *error = NULL;
633 g_spawn_async ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error );
634 if ( error != NULL ) {
635 char *msg = g_strdup_printf (
"Failed to execute action for window: '%s'\nError: '%s'", window_regex, error->message );
639 g_error_free ( error );
648 static ModeMode window_mode_result (
Mode *sw,
int mretv, G_GNUC_UNUSED
char **input,
649 unsigned int selected_line )
662 else if ( ( mretv & (
MENU_OK ) ) ) {
664 act_on_window ( rmpd->ids->array[selected_line] );
670 uint32_t wmdesktop = 0;
671 xcb_get_property_cookie_t cookie;
672 xcb_get_property_reply_t *r;
674 unsigned int current_desktop = 0;
676 if ( !xcb_ewmh_get_current_desktop_reply ( &
xcb->
ewmh, c, ¤t_desktop, NULL ) ) {
680 cookie = xcb_get_property (
xcb->
connection, 0, rmpd->ids->array[selected_line],
681 xcb->
ewmh._NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 0, 1 );
682 r = xcb_get_property_reply (
xcb->
connection, cookie, NULL );
683 if ( r && r->type == XCB_ATOM_CARDINAL ) {
684 wmdesktop = *( (uint32_t *) xcb_get_property_value ( r ) );
686 if ( r && r->type != XCB_ATOM_CARDINAL ) {
688 wmdesktop = current_desktop;
693 if ( wmdesktop != current_desktop ) {
694 xcb_ewmh_request_change_current_desktop ( &
xcb->
ewmh,
699 xcb_ewmh_request_change_active_window ( &
xcb->
ewmh,
xcb->
screen_nbr, rmpd->ids->array[selected_line],
700 XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER,
706 xcb_ewmh_request_close_window ( &(
xcb->
ewmh ),
xcb->
screen_nbr, rmpd->ids->array[selected_line], XCB_CURRENT_TIME, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER );
712 static void window_mode_destroy (
Mode *sw )
715 if ( rmpd != NULL ) {
716 winlist_free ( rmpd->ids );
718 g_free ( rmpd->cache );
719 g_regex_unref ( rmpd->window_regex );
726 const ModeModePrivateData *pd;
730 static void helper_eval_add_str ( GString *str,
const char *input,
int l,
int max_len )
733 const char *input_nn = input ? input :
"";
735 int nc = g_utf8_strlen ( input_nn, -1 );
738 spaces = MAX ( 0, max_len - nc );
739 g_string_append ( str, input_nn );
743 int bl = g_utf8_offset_to_pointer ( input_nn, l ) - input_nn;
744 g_string_append_len ( str, input_nn, bl );
748 g_string_append ( str, input_nn );
752 g_string_append_c ( str,
' ' );
755 static gboolean helper_eval_cb (
const GMatchInfo *info, GString *str, gpointer data )
757 struct arg *d = (
struct arg *) data;
760 match = g_match_info_fetch ( info, 0 );
761 if ( match != NULL ) {
763 if ( match[2] ==
':' ) {
764 l = (int) g_ascii_strtoll ( &match[3], NULL, 10 );
772 if ( match[1] ==
'w' ) {
773 helper_eval_add_str ( str, d->c->wmdesktopstr, l, d->pd->wmdn_len );
775 else if ( match[1] ==
'c' ) {
776 helper_eval_add_str ( str, d->c->class, l, d->pd->clf_len );
778 else if ( match[1] ==
't' ) {
779 helper_eval_add_str ( str, d->c->title, l, d->pd->title_len );
781 else if ( match[1] ==
'n' ) {
782 helper_eval_add_str ( str, d->c->name, l, d->pd->name_len );
784 else if ( match[1] ==
'r' ) {
785 helper_eval_add_str ( str, d->c->role, l, d->pd->role_len );
791 static char * _generate_display_string (
const ModeModePrivateData *pd, client *c )
793 struct arg d = { pd, c };
795 helper_eval_cb, &d, NULL );
796 return g_strchomp ( res );
799 static char *
_get_display_value (
const Mode *sw,
unsigned int selected_line,
int *state, G_GNUC_UNUSED GList **list,
int get_entry )
802 client *c = window_client ( rmpd, rmpd->ids->array[selected_line] );
804 return get_entry ? g_strdup (
"Window has fanished" ) : NULL;
812 return get_entry ? _generate_display_string ( rmpd, c ) : NULL;
818 static cairo_user_data_key_t data_key;
825 static cairo_surface_t * draw_surface_from_data (
int width,
int height, uint32_t *data )
827 unsigned long int len = width * height;
829 uint32_t *buffer = g_new0 ( uint32_t, len );
830 cairo_surface_t *surface;
833 for ( i = 0; i < len; i++ ) {
834 uint8_t a = ( data[i] >> 24 ) & 0xff;
835 double alpha = a / 255.0;
836 uint8_t r = ( ( data[i] >> 16 ) & 0xff ) * alpha;
837 uint8_t g = ( ( data[i] >> 8 ) & 0xff ) * alpha;
838 uint8_t b = ( ( data[i] >> 0 ) & 0xff ) * alpha;
839 buffer[i] = ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b;
842 surface = cairo_image_surface_create_for_data ( (
unsigned char *) buffer,
848 cairo_surface_set_user_data ( surface, &data_key, buffer, g_free );
852 static cairo_surface_t * ewmh_window_icon_from_reply ( xcb_get_property_reply_t *r, uint32_t preferred_size )
854 uint32_t *data, *end, *found_data = 0;
855 uint32_t found_size = 0;
857 if ( !r || r->type != XCB_ATOM_CARDINAL || r->format != 32 || r->length < 2 ) {
861 data = (uint32_t *) xcb_get_property_value ( r );
866 end = data + r->length;
872 while ( data + 1 < end ) {
874 uint64_t data_size = (uint64_t) data[0] * data[1];
875 if ( data_size > (uint64_t) ( end - data - 2 ) ) {
880 uint32_t size = MAX ( data[0], data[1] );
883 gboolean found_icon_too_small = found_size < preferred_size;
884 gboolean found_icon_too_large = found_size > preferred_size;
885 gboolean icon_empty = data[0] == 0 || data[1] == 0;
886 gboolean better_because_bigger = found_icon_too_small && size > found_size;
887 gboolean better_because_smaller = found_icon_too_large &&
888 size >= preferred_size && size < found_size;
889 if ( !icon_empty && ( better_because_bigger || better_because_smaller || found_size == 0 ) ) {
894 data += data_size + 2;
901 return draw_surface_from_data ( found_data[0], found_data[1], found_data + 2 );
904 static cairo_surface_t * get_net_wm_icon ( xcb_window_t xid, uint32_t preferred_size )
906 xcb_get_property_cookie_t cookie = xcb_get_property_unchecked (
908 xcb->
ewmh._NET_WM_ICON, XCB_ATOM_CARDINAL, 0, UINT32_MAX );
909 xcb_get_property_reply_t *r = xcb_get_property_reply (
xcb->
connection, cookie, NULL );
910 cairo_surface_t *surface = ewmh_window_icon_from_reply ( r, preferred_size );
914 static cairo_surface_t *_get_icon (
const Mode *sw,
unsigned int selected_line,
int size )
917 client *c = window_client ( rmpd, rmpd->ids->array[selected_line] );
920 c->thumbnail_checked = TRUE;
922 if ( c->icon == NULL && c->icon_checked == FALSE ) {
923 c->icon = get_net_wm_icon ( rmpd->ids->array[selected_line], size );
924 c->icon_checked = TRUE;
926 if ( c->icon == NULL && c->class ) {
927 if ( c->icon_fetch_uid > 0 ) {
940 .cfg_name_key =
"display-window",
941 ._init = window_mode_init,
942 ._get_num_entries = window_mode_get_num_entries,
943 ._result = window_mode_result,
944 ._destroy = window_mode_destroy,
945 ._token_match = window_match,
947 ._get_icon = _get_icon,
948 ._get_completion = NULL,
949 ._preprocess_input = NULL,
950 .private_data = NULL,
953 Mode window_mode_cd =
956 .cfg_name_key =
"display-windowcd",
957 ._init = window_mode_init_cd,
958 ._get_num_entries = window_mode_get_num_entries,
959 ._result = window_mode_result,
960 ._destroy = window_mode_destroy,
961 ._token_match = window_match,
963 ._get_icon = _get_icon,
964 ._get_completion = NULL,
965 ._preprocess_input = NULL,
966 .private_data = NULL,
970 #endif // WINDOW_MODE