rofi/source/xcb.c
Quentin Glidic 5c5665efca
Revert "xcb: Add XKB_NEW_KEYBOARD_NOTIFY support"
This reverts commit e9532f584f.
It should not matter for rofi, which is not meant to stay around long,
and it messes with XTST (that tools like xscape use).

Signed-off-by: Quentin Glidic <sardemff7+git@sardemff7.net>
2018-01-18 17:09:09 +01:00

1288 lines
44 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* rofi
*
* MIT/X11 License
* Copyright © 2012 Sean Pringle <sean.pringle@gmail.com>
* Copyright © 2013-2017 Qball Cow <qball@gmpclient.org>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
/** Log domain for this module */
#define G_LOG_DOMAIN "X11Helper"
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <glib.h>
#include <cairo.h>
#include <cairo-xcb.h>
#include <librsvg/rsvg.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/randr.h>
#include <xcb/xinerama.h>
#include <xcb/xcb_ewmh.h>
#include <xcb/xproto.h>
#include <xcb/xkb.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#define SN_API_NOT_YET_FROZEN
/* This function is declared as sn_launcher_context_set_application_id but implemented as sn_launcher_set_application_id */
#define sn_launcher_context_set_application_id sn_launcher_set_application_id
#include "rofi-types.h"
#include <libsn/sn.h>
#include "display.h"
#include "xcb-internal.h"
#include "xcb.h"
#include "settings.h"
#include "helper.h"
#include "timings.h"
#include <rofi.h>
/** Checks if the if x and y is inside rectangle. */
#define INTERSECT( x, y, x1, y1, w1, h1 ) ( ( ( ( x ) >= ( x1 ) ) && ( ( x ) < ( x1 + w1 ) ) ) && ( ( ( y ) >= ( y1 ) ) && ( ( y ) < ( y1 + h1 ) ) ) )
WindowManagerQuirk current_window_manager = WM_EWHM;
/**
* Structure holding xcb objects needed to function.
*/
struct _xcb_stuff xcb_int = {
.connection = NULL,
.screen = NULL,
.screen_nbr = -1,
.sndisplay = NULL,
.sncontext = NULL,
.monitors = NULL
};
xcb_stuff *xcb = &xcb_int;
/**
* Depth of root window.
*/
xcb_depth_t *depth = NULL;
xcb_visualtype_t *visual = NULL;
xcb_colormap_t map = XCB_COLORMAP_NONE;
/**
* Visual of the root window.
*/
static xcb_visualtype_t *root_visual = NULL;
xcb_atom_t netatoms[NUM_NETATOMS];
const char *netatom_names[] = { EWMH_ATOMS ( ATOM_CHAR ) };
/**
* Holds for each supported modifier the possible modifier mask.
* Check x11_mod_masks[MODIFIER]&mask != 0 to see if MODIFIER is activated.
*/
cairo_surface_t *x11_helper_get_screenshot_surface ( void )
{
return cairo_xcb_surface_create ( xcb->connection,
xcb_stuff_get_root_window (), root_visual,
xcb->screen->width_in_pixels, xcb->screen->height_in_pixels );
}
static xcb_pixmap_t get_root_pixmap ( xcb_connection_t *c,
xcb_screen_t *screen,
xcb_atom_t atom )
{
xcb_get_property_cookie_t cookie;
xcb_get_property_reply_t *reply;
xcb_pixmap_t rootpixmap = XCB_NONE;
cookie = xcb_get_property ( c,
0,
screen->root,
atom,
XCB_ATOM_PIXMAP,
0,
1 );
reply = xcb_get_property_reply ( c, cookie, NULL );
if ( reply ) {
if ( xcb_get_property_value_length ( reply ) == sizeof ( xcb_pixmap_t ) ) {
memcpy ( &rootpixmap, xcb_get_property_value ( reply ), sizeof ( xcb_pixmap_t ) );
}
free ( reply );
}
return rootpixmap;
}
cairo_surface_t * x11_helper_get_bg_surface ( void )
{
xcb_pixmap_t pm = get_root_pixmap ( xcb->connection, xcb->screen, netatoms[ESETROOT_PMAP_ID] );
if ( pm == XCB_NONE ) {
return NULL;
}
return cairo_xcb_surface_create ( xcb->connection, pm, root_visual,
xcb->screen->width_in_pixels, xcb->screen->height_in_pixels );
}
// retrieve a text property from a window
// technically we could use window_get_prop(), but this is better for character set support
char* window_get_text_prop ( xcb_window_t w, xcb_atom_t atom )
{
xcb_get_property_cookie_t c = xcb_get_property ( xcb->connection, 0, w, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX );
xcb_get_property_reply_t *r = xcb_get_property_reply ( xcb->connection, c, NULL );
if ( r ) {
if ( xcb_get_property_value_length ( r ) > 0 ) {
char *str = NULL;
if ( r->type == netatoms[UTF8_STRING] ) {
str = g_strndup ( xcb_get_property_value ( r ), xcb_get_property_value_length ( r ) );
}
else if ( r->type == netatoms[STRING] ) {
str = rofi_latin_to_utf8_strdup ( xcb_get_property_value ( r ), xcb_get_property_value_length ( r ) );
}
else {
str = g_strdup ( "Invalid encoding." );
}
free ( r );
return str;
}
free ( r );
}
return NULL;
}
void window_set_atom_prop ( xcb_window_t w, xcb_atom_t prop, xcb_atom_t *atoms, int count )
{
xcb_change_property ( xcb->connection, XCB_PROP_MODE_REPLACE, w, prop, XCB_ATOM_ATOM, 32, count, atoms );
}
/****
* Code used to get monitor layout.
*/
/**
* Free monitor structure.
*/
static void x11_monitor_free ( workarea *m )
{
g_free ( m->name );
g_free ( m );
}
static void x11_monitors_free ( void )
{
while ( xcb->monitors != NULL ) {
workarea *m = xcb->monitors;
xcb->monitors = m->next;
x11_monitor_free ( m );
}
}
/**
* Create monitor based on output id
*/
static workarea * x11_get_monitor_from_output ( xcb_randr_output_t out )
{
xcb_randr_get_output_info_reply_t *op_reply;
xcb_randr_get_crtc_info_reply_t *crtc_reply;
xcb_randr_get_output_info_cookie_t it = xcb_randr_get_output_info ( xcb->connection, out, XCB_CURRENT_TIME );
op_reply = xcb_randr_get_output_info_reply ( xcb->connection, it, NULL );
if ( op_reply->crtc == XCB_NONE ) {
free ( op_reply );
return NULL;
}
xcb_randr_get_crtc_info_cookie_t ct = xcb_randr_get_crtc_info ( xcb->connection, op_reply->crtc, XCB_CURRENT_TIME );
crtc_reply = xcb_randr_get_crtc_info_reply ( xcb->connection, ct, NULL );
if ( !crtc_reply ) {
free ( op_reply );
return NULL;
}
workarea *retv = g_malloc0 ( sizeof ( workarea ) );
retv->x = crtc_reply->x;
retv->y = crtc_reply->y;
retv->w = crtc_reply->width;
retv->h = crtc_reply->height;
retv->mw = op_reply->mm_width;
retv->mh = op_reply->mm_height;
char *tname = (char *) xcb_randr_get_output_info_name ( op_reply );
int tname_len = xcb_randr_get_output_info_name_length ( op_reply );
retv->name = g_malloc0 ( ( tname_len + 1 ) * sizeof ( char ) );
memcpy ( retv->name, tname, tname_len );
free ( crtc_reply );
free ( op_reply );
return retv;
}
static int x11_is_extension_present ( const char *extension )
{
xcb_query_extension_cookie_t randr_cookie = xcb_query_extension ( xcb->connection, strlen ( extension ), extension );
xcb_query_extension_reply_t *randr_reply = xcb_query_extension_reply ( xcb->connection, randr_cookie, NULL );
int present = randr_reply->present;
free ( randr_reply );
return present;
}
static void x11_build_monitor_layout_xinerama ()
{
xcb_xinerama_query_screens_cookie_t screens_cookie = xcb_xinerama_query_screens_unchecked (
xcb->connection
);
xcb_xinerama_query_screens_reply_t *screens_reply = xcb_xinerama_query_screens_reply (
xcb->connection,
screens_cookie,
NULL
);
xcb_xinerama_screen_info_iterator_t screens_iterator = xcb_xinerama_query_screens_screen_info_iterator (
screens_reply
);
for (; screens_iterator.rem > 0; xcb_xinerama_screen_info_next ( &screens_iterator ) ) {
workarea *w = g_malloc0 ( sizeof ( workarea ) );
w->x = screens_iterator.data->x_org;
w->y = screens_iterator.data->y_org;
w->w = screens_iterator.data->width;
w->h = screens_iterator.data->height;
w->next = xcb->monitors;
xcb->monitors = w;
}
int index = 0;
for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
iter->monitor_id = index++;
}
free ( screens_reply );
}
static void x11_build_monitor_layout ()
{
if ( xcb->monitors ) {
return;
}
// If RANDR is not available, try Xinerama
if ( !x11_is_extension_present ( "RANDR" ) ) {
// Check if xinerama is available.
if ( x11_is_extension_present ( "XINERAMA" ) ) {
g_debug ( "Query XINERAMA for monitor layout." );
x11_build_monitor_layout_xinerama ();
return;
}
g_debug ( "No RANDR or Xinerama available for getting monitor layout." );
return;
}
g_debug ( "Query RANDR for monitor layout." );
xcb_randr_get_screen_resources_current_reply_t *res_reply;
xcb_randr_get_screen_resources_current_cookie_t src;
src = xcb_randr_get_screen_resources_current ( xcb->connection, xcb->screen->root );
res_reply = xcb_randr_get_screen_resources_current_reply ( xcb->connection, src, NULL );
if ( !res_reply ) {
return; //just report error
}
int mon_num = xcb_randr_get_screen_resources_current_outputs_length ( res_reply );
xcb_randr_output_t *ops = xcb_randr_get_screen_resources_current_outputs ( res_reply );
// Get primary.
xcb_randr_get_output_primary_cookie_t pc = xcb_randr_get_output_primary ( xcb->connection, xcb->screen->root );
xcb_randr_get_output_primary_reply_t *pc_rep = xcb_randr_get_output_primary_reply ( xcb->connection, pc, NULL );
for ( int i = mon_num - 1; i >= 0; i-- ) {
workarea *w = x11_get_monitor_from_output ( ops[i] );
if ( w ) {
w->next = xcb->monitors;
xcb->monitors = w;
if ( pc_rep && pc_rep->output == ops[i] ) {
w->primary = TRUE;
}
}
}
// Number monitor
int index = 0;
for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
iter->monitor_id = index++;
}
// If exists, free primary output reply.
if ( pc_rep ) {
free ( pc_rep );
}
free ( res_reply );
}
void display_dump_monitor_layout ( void )
{
int is_term = isatty ( fileno ( stdout ) );
printf ( "Monitor layout:\n" );
for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
printf ( "%s ID%s: %d", ( is_term ) ? color_bold : "", is_term ? color_reset : "", iter->monitor_id );
if ( iter->primary ) {
printf ( " (primary)" );
}
printf ( "\n" );
printf ( "%s name%s: %s\n", ( is_term ) ? color_bold : "", is_term ? color_reset : "", iter->name );
printf ( "%s position%s: %d,%d\n", ( is_term ) ? color_bold : "", is_term ? color_reset : "", iter->x, iter->y );
printf ( "%s size%s: %d,%d\n", ( is_term ) ? color_bold : "", is_term ? color_reset : "", iter->w, iter->h );
if ( iter->mw > 0 && iter->mh > 0 ) {
printf ( "%s size%s: %dmm,%dmm dpi: %.0f,%.0f\n",
( is_term ) ? color_bold : "",
is_term ? color_reset : "",
iter->mw,
iter->mh,
iter->w * 25.4 / (double) iter->mw,
iter->h * 25.4 / (double) iter->mh
);
}
printf ( "\n" );
}
}
void display_startup_notification ( RofiHelperExecuteContext *context, GSpawnChildSetupFunc *child_setup, gpointer *user_data )
{
if ( context == NULL ) {
return;
}
SnLauncherContext *sncontext;
sncontext = sn_launcher_context_new ( xcb->sndisplay, xcb->screen_nbr );
sn_launcher_context_set_name ( sncontext, context->name );
sn_launcher_context_set_description ( sncontext, context->description );
if ( context->binary != NULL ) {
sn_launcher_context_set_binary_name ( sncontext, context->binary );
}
if ( context->icon != NULL ) {
sn_launcher_context_set_icon_name ( sncontext, context->icon );
}
if ( context->app_id != NULL ) {
sn_launcher_context_set_application_id ( sncontext, context->app_id );
}
if ( context->wmclass != NULL ) {
sn_launcher_context_set_wmclass ( sncontext, context->wmclass );
}
xcb_get_property_cookie_t c;
unsigned int current_desktop = 0;
c = xcb_ewmh_get_current_desktop ( &xcb->ewmh, xcb->screen_nbr );
if ( xcb_ewmh_get_current_desktop_reply ( &xcb->ewmh, c, &current_desktop, NULL ) ) {
sn_launcher_context_set_workspace ( sncontext, current_desktop );
}
sn_launcher_context_initiate ( sncontext, "rofi", context->command, xcb->last_timestamp );
*child_setup = (GSpawnChildSetupFunc) sn_launcher_context_setup_child_process;
*user_data = sncontext;
}
static int monitor_get_dimension ( int monitor_id, workarea *mon )
{
memset ( mon, 0, sizeof ( workarea ) );
mon->w = xcb->screen->width_in_pixels;
mon->h = xcb->screen->height_in_pixels;
workarea *iter = NULL;
for ( iter = xcb->monitors; iter; iter = iter->next ) {
if ( iter->monitor_id == monitor_id ) {
*mon = *iter;
return TRUE;
}
}
return FALSE;
}
// find the dimensions of the monitor displaying point x,y
static void monitor_dimensions ( int x, int y, workarea *mon )
{
memset ( mon, 0, sizeof ( workarea ) );
mon->w = xcb->screen->width_in_pixels;
mon->h = xcb->screen->height_in_pixels;
for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
if ( INTERSECT ( x, y, iter->x, iter->y, iter->w, iter->h ) ) {
*mon = *iter;
break;
}
}
}
/**
* @param root The X11 window used to find the pointer position. Usually the root window.
* @param x The x position of the mouse [out]
* @param y The y position of the mouse [out]
*
* find mouse pointer location
*
* @returns TRUE when found, FALSE otherwise
*/
static int pointer_get ( xcb_window_t root, int *x, int *y )
{
*x = 0;
*y = 0;
xcb_query_pointer_cookie_t c = xcb_query_pointer ( xcb->connection, root );
xcb_query_pointer_reply_t *r = xcb_query_pointer_reply ( xcb->connection, c, NULL );
if ( r ) {
*x = r->root_x;
*y = r->root_y;
free ( r );
return TRUE;
}
return FALSE;
}
static int monitor_active_from_winid ( xcb_drawable_t id, workarea *mon )
{
xcb_window_t root = xcb->screen->root;
xcb_get_geometry_cookie_t c = xcb_get_geometry ( xcb->connection, id );
xcb_get_geometry_reply_t *r = xcb_get_geometry_reply ( xcb->connection, c, NULL );
if ( r ) {
xcb_translate_coordinates_cookie_t ct = xcb_translate_coordinates ( xcb->connection, id, root, r->x, r->y );
xcb_translate_coordinates_reply_t *t = xcb_translate_coordinates_reply ( xcb->connection, ct, NULL );
if ( t ) {
// place the menu above the window
// if some window is focused, place menu above window, else fall
// back to selected monitor.
mon->x = t->dst_x - r->x;
mon->y = t->dst_y - r->y;
mon->w = r->width;
mon->h = r->height;
free ( r );
free ( t );
return TRUE;
}
free ( r );
}
return FALSE;
}
static int monitor_active_from_id_focused ( int mon_id, workarea *mon )
{
int retv = FALSE;
xcb_window_t active_window;
xcb_get_property_cookie_t awc;
awc = xcb_ewmh_get_active_window ( &xcb->ewmh, xcb->screen_nbr );
if ( !xcb_ewmh_get_active_window_reply ( &xcb->ewmh, awc, &active_window, NULL ) ) {
g_debug ( "Failed to get active window, falling back to mouse location (-5)." );
return retv;
}
xcb_query_tree_cookie_t tree_cookie = xcb_query_tree ( xcb->connection, active_window );
xcb_query_tree_reply_t *tree_reply = xcb_query_tree_reply ( xcb->connection, tree_cookie, NULL );
if ( !tree_reply ) {
g_debug ( "Failed to get parent window, falling back to mouse location (-5)." );
return retv;
}
// get geometry.
xcb_get_geometry_cookie_t c = xcb_get_geometry ( xcb->connection, active_window );
xcb_get_geometry_reply_t *r = xcb_get_geometry_reply ( xcb->connection, c, NULL );
if ( !r ) {
g_debug ( "Failed to get geometry of active window, falling back to mouse location (-5)." );
free ( tree_reply );
return retv;
}
xcb_translate_coordinates_cookie_t ct = xcb_translate_coordinates ( xcb->connection, tree_reply->parent, r->root, r->x, r->y );
xcb_translate_coordinates_reply_t *t = xcb_translate_coordinates_reply ( xcb->connection, ct, NULL );
if ( t ) {
if ( mon_id == -2 ) {
// place the menu above the window
// if some window is focused, place menu above window, else fall
// back to selected monitor.
mon->x = t->dst_x - r->x;
mon->y = t->dst_y - r->y;
mon->w = r->width;
mon->h = r->height;
retv = TRUE;
}
else if ( mon_id == -4 ) {
monitor_dimensions ( t->dst_x, t->dst_y, mon );
retv = TRUE;
}
free ( t );
}
else {
g_debug ( "Failed to get translate position of active window, falling back to mouse location (-5)." );
}
free ( r );
free ( tree_reply );
return retv;
}
static int monitor_active_from_id ( int mon_id, workarea *mon )
{
xcb_window_t root = xcb->screen->root;
int x, y;
// At mouse position.
if ( mon_id == -3 ) {
if ( pointer_get ( root, &x, &y ) ) {
monitor_dimensions ( x, y, mon );
mon->x = x;
mon->y = y;
return TRUE;
}
}
// Focused monitor
else if ( mon_id == -1 ) {
// Get the current desktop.
unsigned int current_desktop = 0;
xcb_get_property_cookie_t gcdc;
gcdc = xcb_ewmh_get_current_desktop ( &xcb->ewmh, xcb->screen_nbr );
if ( xcb_ewmh_get_current_desktop_reply ( &xcb->ewmh, gcdc, &current_desktop, NULL ) ) {
xcb_get_property_cookie_t c = xcb_ewmh_get_desktop_viewport ( &xcb->ewmh, xcb->screen_nbr );
xcb_ewmh_get_desktop_viewport_reply_t vp;
if ( xcb_ewmh_get_desktop_viewport_reply ( &xcb->ewmh, c, &vp, NULL ) ) {
if ( current_desktop < vp.desktop_viewport_len ) {
monitor_dimensions ( vp.desktop_viewport[current_desktop].x,
vp.desktop_viewport[current_desktop].y, mon );
xcb_ewmh_get_desktop_viewport_reply_wipe ( &vp );
return TRUE;
}
else {
g_debug ( "Viewport does not exist for current desktop: %d, falling back to mouse location (-5)", current_desktop );
}
xcb_ewmh_get_desktop_viewport_reply_wipe ( &vp );
}
else {
g_debug ( "Failed to get viewport for current desktop: %d, falling back to mouse location (-5).", current_desktop );
}
}
else {
g_debug ( "Failed to get current desktop, falling back to mouse location (-5)." );
}
}
else if ( mon_id == -2 || mon_id == -4 ) {
if ( monitor_active_from_id_focused ( mon_id, mon ) ) {
return TRUE;
}
}
// Monitor that has mouse pointer.
else if ( mon_id == -5 ) {
if ( pointer_get ( root, &x, &y ) ) {
monitor_dimensions ( x, y, mon );
return TRUE;
}
// This is our give up point.
return FALSE;
}
g_debug ( "Failed to find monitor, fall back to monitor showing mouse." );
return monitor_active_from_id ( -5, mon );
}
// determine which monitor holds the active window, or failing that the mouse pointer
int monitor_active ( workarea *mon )
{
if ( config.monitor != NULL ) {
for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
if ( g_strcmp0 ( config.monitor, iter->name ) == 0 ) {
*mon = *iter;
return TRUE;
}
}
}
// Grab primary.
if ( g_strcmp0 ( config.monitor, "primary" ) == 0 ) {
for ( workarea *iter = xcb->monitors; iter; iter = iter->next ) {
if ( iter->primary ) {
*mon = *iter;
return TRUE;
}
}
}
if ( g_str_has_prefix ( config.monitor, "wid:" ) ) {
char *end = NULL;
xcb_drawable_t win = g_ascii_strtoll ( config.monitor + 4, &end, 0 );
if ( end != config.monitor ) {
if ( monitor_active_from_winid ( win, mon ) ) {
return TRUE;
}
}
}
{
// IF fail, fall back to classic mode.
char *end = NULL;
gint64 mon_id = g_ascii_strtoll ( config.monitor, &end, 0 );
if ( end != config.monitor ) {
if ( mon_id >= 0 ) {
if ( monitor_get_dimension ( mon_id, mon ) ) {
return TRUE;
}
g_warning ( "Failed to find selected monitor." );
}
else {
return monitor_active_from_id ( mon_id, mon );
}
}
}
// Fallback.
monitor_dimensions ( 0, 0, mon );
return FALSE;
}
/**
* @param state Internal state of the menu.
* @param xse X selection event.
*
* Handle paste event.
*/
static void rofi_view_paste ( RofiViewState *state, xcb_selection_notify_event_t *xse )
{
if ( xse->property == XCB_ATOM_NONE ) {
g_warning ( "Failed to convert selection" );
}
else if ( xse->property == xcb->ewmh.UTF8_STRING ) {
gchar *text = window_get_text_prop ( xse->requestor, xcb->ewmh.UTF8_STRING );
if ( text != NULL && text[0] != '\0' ) {
unsigned int dl = strlen ( text );
// Strip new line
for ( unsigned int i = 0; i < dl; i++ ) {
if ( text[i] == '\n' ) {
text[i] = '\0';
}
}
rofi_view_handle_text ( state, text );
}
g_free ( text );
}
else {
g_warning ( "Failed" );
}
}
static gboolean x11_button_to_nk_bindings_button ( guint32 x11_button, NkBindingsMouseButton *button )
{
switch ( x11_button )
{
case 1:
*button = NK_BINDINGS_MOUSE_BUTTON_PRIMARY;
break;
case 3:
*button = NK_BINDINGS_MOUSE_BUTTON_SECONDARY;
break;
case 2:
*button = NK_BINDINGS_MOUSE_BUTTON_MIDDLE;
break;
case 8:
*button = NK_BINDINGS_MOUSE_BUTTON_BACK;
break;
case 9:
*button = NK_BINDINGS_MOUSE_BUTTON_FORWARD;
break;
case 4:
case 5:
case 6:
case 7:
return FALSE;
default:
*button = NK_BINDINGS_MOUSE_BUTTON_EXTRA + x11_button;
}
return TRUE;
}
static gboolean x11_button_to_nk_bindings_scroll ( guint32 x11_button, NkBindingsScrollAxis *axis, gint32 *steps )
{
*steps = 1;
switch ( x11_button )
{
case 4:
*steps = -1;
/* fallthrough */
case 5:
*axis = NK_BINDINGS_SCROLL_AXIS_VERTICAL;
break;
case 6:
*steps = -1;
/* fallthrough */
case 7:
*axis = NK_BINDINGS_SCROLL_AXIS_HORIZONTAL;
break;
default:
return FALSE;
}
return TRUE;
}
/**
* Process X11 events in the main-loop (gui-thread) of the application.
*/
static void main_loop_x11_event_handler_view ( xcb_generic_event_t *event )
{
RofiViewState *state = rofi_view_get_active ();
if ( state == NULL ) {
return;
}
switch ( event->response_type & ~0x80 )
{
case XCB_EXPOSE:
rofi_view_frame_callback ();
break;
case XCB_CONFIGURE_NOTIFY:
{
xcb_configure_notify_event_t *xce = (xcb_configure_notify_event_t *) event;
rofi_view_temp_configure_notify ( state, xce );
break;
}
case XCB_MOTION_NOTIFY:
{
if ( config.click_to_exit == TRUE ) {
xcb->mouse_seen = TRUE;
}
xcb_motion_notify_event_t *xme = (xcb_motion_notify_event_t *) event;
rofi_view_handle_mouse_motion ( state, xme->event_x, xme->event_y );
break;
}
case XCB_BUTTON_PRESS:
{
xcb_button_press_event_t *bpe = (xcb_button_press_event_t *) event;
NkBindingsMouseButton button;
NkBindingsScrollAxis axis;
gint32 steps;
xcb->last_timestamp = bpe->time;
rofi_view_handle_mouse_motion ( state, bpe->event_x, bpe->event_y );
if ( x11_button_to_nk_bindings_button ( bpe->detail, &button ) )
nk_bindings_seat_handle_button ( xcb->bindings_seat, NULL, button, NK_BINDINGS_BUTTON_STATE_PRESS, bpe->time );
else if ( x11_button_to_nk_bindings_scroll ( bpe->detail, &axis, &steps) )
nk_bindings_seat_handle_scroll ( xcb->bindings_seat, NULL, axis, steps );
break;
}
case XCB_BUTTON_RELEASE:
{
xcb_button_release_event_t *bre = (xcb_button_release_event_t *) event;
NkBindingsMouseButton button;
xcb->last_timestamp = bre->time;
if ( x11_button_to_nk_bindings_button ( bre->detail, &button ) )
nk_bindings_seat_handle_button ( xcb->bindings_seat, NULL, button, NK_BINDINGS_BUTTON_STATE_RELEASE, bre->time );
if ( config.click_to_exit == TRUE ) {
if ( !xcb->mouse_seen ) {
rofi_view_temp_click_to_exit ( state, bre->event );
}
xcb->mouse_seen = FALSE;
}
break;
}
// Paste event.
case XCB_SELECTION_NOTIFY:
rofi_view_paste ( state, (xcb_selection_notify_event_t *) event );
break;
case XCB_KEYMAP_NOTIFY:
{
xcb_keymap_notify_event_t *kne = (xcb_keymap_notify_event_t *) event;
for ( gint32 by = 0; by < 31; ++by ) {
for ( gint8 bi = 0; bi < 7; ++bi ) {
if ( kne->keys[by] & ( 1 << bi ) ) {
// X11 keycodes starts at 8
nk_bindings_seat_handle_key ( xcb->bindings_seat, NULL, ( 8 * by + bi ) + 8, NK_BINDINGS_KEY_STATE_PRESSED );
}
}
}
break;
}
case XCB_KEY_PRESS:
{
xcb_key_press_event_t *xkpe = (xcb_key_press_event_t *) event;
gchar *text;
xcb->last_timestamp = xkpe->time;
text = nk_bindings_seat_handle_key_with_modmask ( xcb->bindings_seat, NULL, xkpe->state, xkpe->detail, NK_BINDINGS_KEY_STATE_PRESS );
if ( text != NULL ) {
rofi_view_handle_text ( state, text );
}
break;
}
case XCB_KEY_RELEASE:
{
xcb_key_release_event_t *xkre = (xcb_key_release_event_t *) event;
xcb->last_timestamp = xkre->time;
nk_bindings_seat_handle_key ( xcb->bindings_seat, NULL, xkre->detail, NK_BINDINGS_KEY_STATE_RELEASE );
break;
}
default:
break;
}
rofi_view_maybe_update ( state );
}
static gboolean main_loop_x11_event_handler ( xcb_generic_event_t *ev, G_GNUC_UNUSED gpointer user_data )
{
if ( ev == NULL ) {
int status = xcb_connection_has_error ( xcb->connection );
if ( status > 0 ) {
g_warning ( "The XCB connection to X server had a fatal error: %d", status );
g_main_loop_quit ( xcb->main_loop );
return G_SOURCE_REMOVE;
}
else {
g_warning ( "main_loop_x11_event_handler: ev == NULL, status == %d", status );
return G_SOURCE_CONTINUE;
}
}
uint8_t type = ev->response_type & ~0x80;
if ( type == xcb->xkb.first_event ) {
switch ( ev->pad0 )
{
case XCB_XKB_MAP_NOTIFY:
{
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 );
struct xkb_state *state = xkb_x11_state_new_from_device ( keymap, xcb->connection, xcb->xkb.device_id );
nk_bindings_seat_update_keymap ( xcb->bindings_seat, keymap, state );
xkb_keymap_unref ( keymap );
xkb_state_unref ( state );
break;
}
case XCB_XKB_STATE_NOTIFY:
{
xcb_xkb_state_notify_event_t *ksne = (xcb_xkb_state_notify_event_t *) ev;
nk_bindings_seat_update_mask ( xcb->bindings_seat, NULL,
ksne->baseMods,
ksne->latchedMods,
ksne->lockedMods,
ksne->baseGroup,
ksne->latchedGroup,
ksne->lockedGroup );
rofi_view_maybe_update ( rofi_view_get_active () );
break;
}
}
return G_SOURCE_CONTINUE;
}
if ( xcb->sndisplay != NULL ) {
sn_xcb_display_process_event ( xcb->sndisplay, ev );
}
main_loop_x11_event_handler_view ( ev );
return G_SOURCE_CONTINUE;
}
static int take_pointer ( xcb_window_t w, int iters )
{
int i = 0;
while ( TRUE ) {
if ( xcb_connection_has_error ( xcb->connection ) ) {
g_warning ( "Connection has error" );
exit ( EXIT_FAILURE );
}
xcb_grab_pointer_cookie_t cc = xcb_grab_pointer ( xcb->connection, 1, w, XCB_EVENT_MASK_BUTTON_RELEASE,
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, w, XCB_NONE, XCB_CURRENT_TIME );
xcb_grab_pointer_reply_t *r = xcb_grab_pointer_reply ( xcb->connection, cc, NULL );
if ( r ) {
if ( r->status == XCB_GRAB_STATUS_SUCCESS ) {
free ( r );
return 1;
}
free ( r );
}
if ( ( ++i ) > iters ) {
break;
}
usleep ( 1000 );
}
return 0;
}
static int take_keyboard ( xcb_window_t w, int iters )
{
int i = 0;
while ( TRUE ) {
if ( xcb_connection_has_error ( xcb->connection ) ) {
g_warning ( "Connection has error" );
exit ( EXIT_FAILURE );
}
xcb_grab_keyboard_cookie_t cc = xcb_grab_keyboard ( xcb->connection,
1, w, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC,
XCB_GRAB_MODE_ASYNC );
xcb_grab_keyboard_reply_t *r = xcb_grab_keyboard_reply ( xcb->connection, cc, NULL );
if ( r ) {
if ( r->status == XCB_GRAB_STATUS_SUCCESS ) {
free ( r );
return 1;
}
free ( r );
}
if ( ( ++i ) > iters ) {
break;
}
usleep ( 1000 );
}
return 0;
}
static void release_keyboard ( void )
{
xcb_ungrab_keyboard ( xcb->connection, XCB_CURRENT_TIME );
}
static void release_pointer ( void )
{
xcb_ungrab_pointer ( xcb->connection, XCB_CURRENT_TIME );
}
/** X server error depth. to handle nested errors. */
static int error_trap_depth = 0;
static void error_trap_push ( G_GNUC_UNUSED SnDisplay *display, G_GNUC_UNUSED xcb_connection_t *xdisplay )
{
++error_trap_depth;
}
static void error_trap_pop ( G_GNUC_UNUSED SnDisplay *display, xcb_connection_t *xdisplay )
{
if ( error_trap_depth == 0 ) {
g_warning ( "Error trap underflow!" );
exit ( EXIT_FAILURE );
}
xcb_flush ( xdisplay );
--error_trap_depth;
}
/**
* Fill in the list of frequently used X11 Atoms.
*/
static void x11_create_frequently_used_atoms ( void )
{
// X atom values
for ( int i = 0; i < NUM_NETATOMS; i++ ) {
xcb_intern_atom_cookie_t cc = xcb_intern_atom ( xcb->connection, 0, strlen ( netatom_names[i] ), netatom_names[i] );
xcb_intern_atom_reply_t *r = xcb_intern_atom_reply ( xcb->connection, cc, NULL );
if ( r ) {
netatoms[i] = r->atom;
free ( r );
}
}
}
static void x11_helper_discover_window_manager ( void )
{
xcb_window_t wm_win = 0;
xcb_get_property_cookie_t cc = xcb_ewmh_get_supporting_wm_check_unchecked ( &xcb->ewmh,
xcb_stuff_get_root_window () );
if ( xcb_ewmh_get_supporting_wm_check_reply ( &xcb->ewmh, cc, &wm_win, NULL ) ) {
xcb_ewmh_get_utf8_strings_reply_t wtitle;
xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name_unchecked ( &( xcb->ewmh ), wm_win );
if ( xcb_ewmh_get_wm_name_reply ( &( xcb->ewmh ), cookie, &wtitle, (void *) 0 ) ) {
if ( wtitle.strings_len > 0 ) {
g_debug ( "Found window manager: %s", wtitle.strings );
if ( g_strcmp0 ( wtitle.strings, "i3" ) == 0 ) {
current_window_manager = WM_DO_NOT_CHANGE_CURRENT_DESKTOP | WM_PANGO_WORKSPACE_NAMES;
}
}
xcb_ewmh_get_utf8_strings_reply_wipe ( &wtitle );
}
}
}
gboolean display_setup ( GMainLoop *main_loop, NkBindings *bindings )
{
// Get DISPLAY, first env, then argument.
// We never modify display_str content.
char *display_str = ( char *) g_getenv ( "DISPLAY" );
find_arg_str ( "-display", &display_str );
xcb->main_loop = main_loop;
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 );
if ( xcb->source == NULL ) {
g_warning ( "Failed to open display: %s", display_str );
return FALSE;
}
xcb->connection = g_water_xcb_source_get_connection ( xcb->source );
TICK_N ( "Open Display" );
xcb->screen = xcb_aux_get_screen ( xcb->connection, xcb->screen_nbr );
x11_build_monitor_layout ();
xcb_intern_atom_cookie_t *ac = xcb_ewmh_init_atoms ( xcb->connection, &xcb->ewmh );
xcb_generic_error_t *errors = NULL;
xcb_ewmh_init_atoms_replies ( &xcb->ewmh, ac, &errors );
if ( errors ) {
g_warning ( "Failed to create EWMH atoms" );
free ( errors );
}
// Discover the current active window manager.
x11_helper_discover_window_manager ();
TICK_N ( "Setup XCB" );
if ( xkb_x11_setup_xkb_extension ( xcb->connection, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, NULL, NULL, &xcb->xkb.first_event, NULL ) < 0 ) {
g_warning ( "cannot setup XKB extension!" );
return FALSE;
}
xcb->xkb.device_id = xkb_x11_get_core_keyboard_device_id ( xcb->connection );
enum
{
required_events =
( XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
XCB_XKB_EVENT_TYPE_STATE_NOTIFY ),
required_nkn_details =
( XCB_XKB_NKN_DETAIL_KEYCODES ),
required_map_parts =
( XCB_XKB_MAP_PART_KEY_TYPES |
XCB_XKB_MAP_PART_KEY_SYMS |
XCB_XKB_MAP_PART_MODIFIER_MAP |
XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
XCB_XKB_MAP_PART_KEY_ACTIONS |
XCB_XKB_MAP_PART_VIRTUAL_MODS |
XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP ),
required_state_details =
( XCB_XKB_STATE_PART_MODIFIER_BASE |
XCB_XKB_STATE_PART_MODIFIER_LATCH |
XCB_XKB_STATE_PART_MODIFIER_LOCK |
XCB_XKB_STATE_PART_GROUP_BASE |
XCB_XKB_STATE_PART_GROUP_LATCH |
XCB_XKB_STATE_PART_GROUP_LOCK ),
};
static const xcb_xkb_select_events_details_t details = {
.affectNewKeyboard = required_nkn_details,
.newKeyboardDetails = required_nkn_details,
.affectState = required_state_details,
.stateDetails = required_state_details,
};
xcb_xkb_select_events ( xcb->connection, xcb->xkb.device_id, required_events, /* affectWhich */
0, /* clear */
required_events, /* selectAll */
required_map_parts, /* affectMap */
required_map_parts, /* map */
&details );
xcb->bindings_seat = nk_bindings_seat_new ( bindings, XKB_CONTEXT_NO_FLAGS );
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 );
if ( keymap == NULL ) {
g_warning ( "Failed to get Keymap for current keyboard device." );
return FALSE;
}
struct xkb_state *state = xkb_x11_state_new_from_device ( keymap, xcb->connection, xcb->xkb.device_id );
if ( state == NULL ) {
g_warning ( "Failed to get state object for current keyboard device." );
return FALSE;
}
nk_bindings_seat_update_keymap ( xcb->bindings_seat, keymap, state );
xkb_state_unref ( state );
xkb_keymap_unref ( keymap );
// determine numlock mask so we can bind on keys with and without it
x11_create_frequently_used_atoms ( );
if ( xcb_connection_has_error ( xcb->connection ) ) {
g_warning ( "Connection has error" );
return FALSE;
}
// startup not.
xcb->sndisplay = sn_xcb_display_new ( xcb->connection, error_trap_push, error_trap_pop );
if ( xcb_connection_has_error ( xcb->connection ) ) {
g_warning ( "Connection has error" );
return FALSE;
}
if ( xcb->sndisplay != NULL ) {
xcb->sncontext = sn_launchee_context_new_from_environment ( xcb->sndisplay, xcb->screen_nbr );
}
if ( xcb_connection_has_error ( xcb->connection ) ) {
g_warning ( "Connection has error" );
return FALSE;
}
return TRUE;
}
static void x11_create_visual_and_colormap ( void )
{
xcb_depth_t *root_depth = NULL;
xcb_depth_iterator_t depth_iter;
for ( depth_iter = xcb_screen_allowed_depths_iterator ( xcb->screen ); depth_iter.rem; xcb_depth_next ( &depth_iter ) ) {
xcb_depth_t *d = depth_iter.data;
xcb_visualtype_iterator_t visual_iter;
for ( visual_iter = xcb_depth_visuals_iterator ( d ); visual_iter.rem; xcb_visualtype_next ( &visual_iter ) ) {
xcb_visualtype_t *v = visual_iter.data;
if ( ( v->bits_per_rgb_value == 8 ) && ( d->depth == 32 ) && ( v->_class == XCB_VISUAL_CLASS_TRUE_COLOR ) ) {
depth = d;
visual = v;
}
if ( xcb->screen->root_visual == v->visual_id ) {
root_depth = d;
root_visual = v;
}
}
}
if ( visual != NULL ) {
xcb_void_cookie_t c;
xcb_generic_error_t *e;
map = xcb_generate_id ( xcb->connection );
c = xcb_create_colormap_checked ( xcb->connection, XCB_COLORMAP_ALLOC_NONE, map, xcb->screen->root, visual->visual_id );
e = xcb_request_check ( xcb->connection, c );
if ( e ) {
depth = NULL;
visual = NULL;
free ( e );
}
}
if ( visual == NULL ) {
depth = root_depth;
visual = root_visual;
map = xcb->screen->default_colormap;
}
}
/** Retry count of grabbing keyboard. */
unsigned int lazy_grab_retry_count_kb = 0;
/** Retry count of grabbing pointer. */
unsigned int lazy_grab_retry_count_pt = 0;
static gboolean lazy_grab_pointer ( G_GNUC_UNUSED gpointer data )
{
// After 5 sec.
if ( lazy_grab_retry_count_pt > ( 5 * 1000 ) ) {
g_warning ( "Failed to grab pointer after %u times. Giving up.", lazy_grab_retry_count_pt );
return G_SOURCE_REMOVE;
}
if ( take_pointer ( xcb_stuff_get_root_window (), 0 ) ) {
return G_SOURCE_REMOVE;
}
lazy_grab_retry_count_pt++;
return G_SOURCE_CONTINUE;
}
static gboolean lazy_grab_keyboard ( G_GNUC_UNUSED gpointer data )
{
// After 5 sec.
if ( lazy_grab_retry_count_kb > ( 5 * 1000 ) ) {
g_warning ( "Failed to grab keyboard after %u times. Giving up.", lazy_grab_retry_count_kb );
g_main_loop_quit ( xcb->main_loop );
return G_SOURCE_REMOVE;
}
if ( take_keyboard ( xcb_stuff_get_root_window (), 0 ) ) {
return G_SOURCE_REMOVE;
}
lazy_grab_retry_count_kb++;
return G_SOURCE_CONTINUE;
}
gboolean display_late_setup ( void )
{
x11_create_visual_and_colormap ();
/**
* Create window (without showing)
*/
// Try to grab the keyboard as early as possible.
// We grab this using the rootwindow (as dmenu does it).
// this seems to result in the smallest delay for most people.
if ( find_arg ( "-normal-window" ) >= 0 ) {
return TRUE;
}
if ( find_arg ( "-no-lazy-grab" ) >= 0 ) {
if ( !take_keyboard ( xcb_stuff_get_root_window (), 500 ) ) {
g_warning ( "Failed to grab keyboard, even after %d uS.", 500 * 1000 );
return FALSE;
}
if ( !take_pointer ( xcb_stuff_get_root_window (), 100 ) ) {
g_warning ( "Failed to grab mouse pointer, even after %d uS.", 100 * 1000 );
}
}
else {
if ( !take_keyboard ( xcb_stuff_get_root_window (), 0 ) ) {
g_timeout_add ( 1, lazy_grab_keyboard, NULL );
}
if ( !take_pointer ( xcb_stuff_get_root_window (), 0 ) ) {
g_timeout_add ( 1, lazy_grab_pointer, NULL );
}
}
return TRUE;
}
xcb_window_t xcb_stuff_get_root_window ( void )
{
return xcb->screen->root;
}
void display_early_cleanup ( void )
{
release_keyboard ( );
release_pointer ( );
xcb_flush ( xcb->connection );
}
void display_cleanup ( void )
{
if ( xcb->connection == NULL ) {
return;
}
g_debug ( "Cleaning up XCB and XKB" );
nk_bindings_seat_free ( xcb->bindings_seat );
if ( xcb->sncontext != NULL ) {
sn_launchee_context_unref ( xcb->sncontext );
xcb->sncontext = NULL;
}
if ( xcb->sndisplay != NULL ) {
sn_display_unref ( xcb->sndisplay );
xcb->sndisplay = NULL;
}
x11_monitors_free ();
xcb_ewmh_connection_wipe ( &( xcb->ewmh ) );
xcb_flush ( xcb->connection );
xcb_aux_sync ( xcb->connection );
g_water_xcb_source_free ( xcb->source );
xcb->source = NULL;
xcb->connection = NULL;
xcb->screen = NULL;
xcb->screen_nbr = 0;
}
void x11_disable_decoration ( xcb_window_t window )
{
// Flag used to indicate we are setting the decoration type.
const uint32_t MWM_HINTS_DECORATIONS = ( 1 << 1 );
// Motif property data structure
struct MotifWMHints
{
uint32_t flags;
uint32_t functions;
uint32_t decorations;
int32_t inputMode;
uint32_t state;
};
struct MotifWMHints hints;
hints.flags = MWM_HINTS_DECORATIONS;
hints.decorations = 0;
hints.functions = 0;
hints.inputMode = 0;
hints.state = 0;
xcb_atom_t ha = netatoms[_MOTIF_WM_HINTS];
xcb_change_property ( xcb->connection, XCB_PROP_MODE_REPLACE, window, ha, ha, 32, 5, &hints );
}