mirror of
https://github.com/lbonn/rofi
synced 2024-11-26 22:00:20 +00:00
2053 lines
63 KiB
C
2053 lines
63 KiB
C
/*
|
||
* rofi
|
||
*
|
||
* MIT/X11 License
|
||
* Copyright © 2012 Sean Pringle <sean.pringle@gmail.com>
|
||
* Copyright © 2013-2023 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"
|
||
|
||
#ifdef XCB_IMDKIT
|
||
#include <xcb-imdkit/encoding.h>
|
||
#endif
|
||
#include <cairo-xcb.h>
|
||
#include <cairo.h>
|
||
#include <config.h>
|
||
#include <glib.h>
|
||
#include <math.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <xcb/randr.h>
|
||
#include <xcb/xcb.h>
|
||
#include <xcb/xcb_aux.h>
|
||
#include <xcb/xcb_cursor.h>
|
||
#include <xcb/xcb_ewmh.h>
|
||
#include <xcb/xinerama.h>
|
||
#include <xcb/xkb.h>
|
||
#include <xcb/xproto.h>
|
||
#include <xkbcommon/xkbcommon-x11.h>
|
||
#include <xkbcommon/xkbcommon.h>
|
||
/** Indicate that we know the startup notification api is not yet stable. */
|
||
#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. Quick Fix. */
|
||
#define sn_launcher_context_set_application_id sn_launcher_set_application_id
|
||
#include "display-internal.h"
|
||
#include "display.h"
|
||
#include "helper.h"
|
||
#include "rofi-types.h"
|
||
#include "settings.h"
|
||
#include "timings.h"
|
||
#include "xcb-internal.h"
|
||
#include "xcb.h"
|
||
#include <libsn/sn.h>
|
||
#include <stdbool.h>
|
||
|
||
#include "mode.h"
|
||
#include "modes/window.h"
|
||
|
||
#include <rofi.h>
|
||
|
||
/** Minimal randr preferred for running rofi (1.5) (Major version number) */
|
||
#define RANDR_PREF_MAJOR_VERSION 1
|
||
/** Minimal randr preferred for running rofi (1.5) (Minor version number) */
|
||
#define RANDR_PREF_MINOR_VERSION 5
|
||
|
||
/** 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,
|
||
#ifdef XCB_IMDKIT
|
||
.im = NULL,
|
||
#endif
|
||
.screen_nbr = -1,
|
||
.sndisplay = NULL,
|
||
.sncontext = NULL,
|
||
.monitors = NULL,
|
||
.clipboard = 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)};
|
||
|
||
/**
|
||
* Cached X11 cursors.
|
||
*/
|
||
xcb_cursor_t cursors[NUM_CURSORS] = {XCB_CURSOR_NONE, XCB_CURSOR_NONE,
|
||
XCB_CURSOR_NONE};
|
||
|
||
/** Mapping between theme name and system name for mouse cursor. */
|
||
const struct {
|
||
/** Theme name */
|
||
const char *css_name;
|
||
/** System name */
|
||
const char *traditional_name;
|
||
} cursor_names[] = {
|
||
{"default", "left_ptr"}, {"pointer", "hand"}, {"text", "xterm"}};
|
||
|
||
static xcb_visualtype_t *lookup_visual(xcb_screen_t *s, xcb_visualid_t visual) {
|
||
xcb_depth_iterator_t d;
|
||
d = xcb_screen_allowed_depths_iterator(s);
|
||
for (; d.rem; xcb_depth_next(&d)) {
|
||
xcb_visualtype_iterator_t v = xcb_depth_visuals_iterator(d.data);
|
||
for (; v.rem; xcb_visualtype_next(&v)) {
|
||
if (v.data->visual_id == visual) {
|
||
return v.data;
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* This blur function was originally created my MacSlow and published on his
|
||
* website: http://macslow.thepimp.net. I'm not entirely sure he's proud of it,
|
||
* but it has proved immeasurably useful for me. */
|
||
|
||
static uint32_t *create_kernel(double radius, double deviation,
|
||
uint32_t *sum2) {
|
||
int size = 2 * (int)(radius) + 1;
|
||
uint32_t *kernel = (uint32_t *)(g_malloc(sizeof(uint32_t) * (size + 1)));
|
||
double radiusf = fabs(radius) + 1.0;
|
||
double value = -radius;
|
||
double sum = 0.0;
|
||
int i;
|
||
|
||
if (deviation == 0.0) {
|
||
deviation = sqrt(-(radiusf * radiusf) / (2.0 * log(1.0 / 255.0)));
|
||
}
|
||
|
||
kernel[0] = size;
|
||
|
||
for (i = 0; i < size; i++) {
|
||
kernel[1 + i] = INT16_MAX / (2.506628275 * deviation) *
|
||
exp(-((value * value) / (2.0 * (deviation * deviation))));
|
||
|
||
sum += kernel[1 + i];
|
||
value += 1.0;
|
||
}
|
||
|
||
*sum2 = sum;
|
||
|
||
return kernel;
|
||
}
|
||
|
||
void cairo_image_surface_blur(cairo_surface_t *surface, double radius,
|
||
double deviation) {
|
||
uint32_t *horzBlur;
|
||
uint32_t *kernel = 0;
|
||
cairo_format_t format;
|
||
unsigned int channels;
|
||
|
||
if (cairo_surface_status(surface)) {
|
||
return;
|
||
}
|
||
|
||
uint8_t *data = cairo_image_surface_get_data(surface);
|
||
format = cairo_image_surface_get_format(surface);
|
||
const int width = cairo_image_surface_get_width(surface);
|
||
const int height = cairo_image_surface_get_height(surface);
|
||
const int stride = cairo_image_surface_get_stride(surface);
|
||
|
||
if (format == CAIRO_FORMAT_ARGB32) {
|
||
channels = 4;
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
horzBlur = (uint32_t *)(g_malloc(sizeof(uint32_t) * height * stride));
|
||
TICK();
|
||
uint32_t sum = 0;
|
||
kernel = create_kernel(radius, deviation, &sum);
|
||
TICK_N("BLUR: kernel");
|
||
|
||
/* Horizontal pass. */
|
||
uint32_t *horzBlur_ptr = horzBlur;
|
||
for (int iY = 0; iY < height; iY++) {
|
||
const int iYs = iY * stride;
|
||
for (int iX = 0; iX < width; iX++) {
|
||
uint32_t red = 0;
|
||
uint32_t green = 0;
|
||
uint32_t blue = 0;
|
||
uint32_t alpha = 0;
|
||
int offset = (int)(kernel[0]) / -2;
|
||
|
||
for (int i = 0; i < (int)(kernel[0]); i++) {
|
||
int x = iX + offset;
|
||
|
||
if (x < 0 || x >= width) {
|
||
offset++;
|
||
continue;
|
||
}
|
||
|
||
uint8_t *dataPtr = &data[iYs + x * channels];
|
||
const uint32_t kernip1 = kernel[i + 1];
|
||
|
||
blue += kernip1 * dataPtr[0];
|
||
green += kernip1 * dataPtr[1];
|
||
red += kernip1 * dataPtr[2];
|
||
alpha += kernip1 * dataPtr[3];
|
||
offset++;
|
||
}
|
||
|
||
*horzBlur_ptr++ = blue / sum;
|
||
*horzBlur_ptr++ = green / sum;
|
||
*horzBlur_ptr++ = red / sum;
|
||
*horzBlur_ptr++ = alpha / sum;
|
||
}
|
||
}
|
||
TICK_N("BLUR: hori");
|
||
|
||
/* Vertical pass. */
|
||
for (int iY = 0; iY < height; iY++) {
|
||
for (int iX = 0; iX < width; iX++) {
|
||
uint32_t red = 0;
|
||
uint32_t green = 0;
|
||
uint32_t blue = 0;
|
||
uint32_t alpha = 0;
|
||
int offset = (int)(kernel[0]) / -2;
|
||
|
||
const int iXs = iX * channels;
|
||
for (int i = 0; i < (int)(kernel[0]); i++) {
|
||
int y = iY + offset;
|
||
|
||
if (y < 0 || y >= height) {
|
||
offset++;
|
||
continue;
|
||
}
|
||
|
||
uint32_t *dataPtr = &horzBlur[y * stride + iXs];
|
||
const uint32_t kernip1 = kernel[i + 1];
|
||
|
||
blue += kernip1 * dataPtr[0];
|
||
green += kernip1 * dataPtr[1];
|
||
red += kernip1 * dataPtr[2];
|
||
alpha += kernip1 * dataPtr[3];
|
||
|
||
offset++;
|
||
}
|
||
|
||
*data++ = blue / sum;
|
||
*data++ = green / sum;
|
||
*data++ = red / sum;
|
||
*data++ = alpha / sum;
|
||
}
|
||
}
|
||
TICK_N("BLUR: vert");
|
||
|
||
free(kernel);
|
||
free(horzBlur);
|
||
|
||
return;
|
||
}
|
||
|
||
cairo_surface_t *x11_helper_get_screenshot_surface_window(xcb_window_t window,
|
||
int size) {
|
||
xcb_get_geometry_cookie_t cookie;
|
||
xcb_get_geometry_reply_t *reply;
|
||
|
||
cookie = xcb_get_geometry(xcb->connection, window);
|
||
reply = xcb_get_geometry_reply(xcb->connection, cookie, NULL);
|
||
if (reply == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
xcb_get_window_attributes_cookie_t attributesCookie =
|
||
xcb_get_window_attributes(xcb->connection, window);
|
||
xcb_get_window_attributes_reply_t *attributes =
|
||
xcb_get_window_attributes_reply(xcb->connection, attributesCookie, NULL);
|
||
if (attributes == NULL || (attributes->map_state != XCB_MAP_STATE_VIEWABLE)) {
|
||
free(reply);
|
||
if (attributes) {
|
||
free(attributes);
|
||
}
|
||
return NULL;
|
||
}
|
||
// Create a cairo surface for the window.
|
||
xcb_visualtype_t *vt = lookup_visual(xcb->screen, attributes->visual);
|
||
free(attributes);
|
||
|
||
cairo_surface_t *t = cairo_xcb_surface_create(xcb->connection, window, vt,
|
||
reply->width, reply->height);
|
||
|
||
if (cairo_surface_status(t) != CAIRO_STATUS_SUCCESS) {
|
||
cairo_surface_destroy(t);
|
||
free(reply);
|
||
return NULL;
|
||
}
|
||
|
||
// Scale the image, as we don't want to keep large one around.
|
||
int max = MAX(reply->width, reply->height);
|
||
double scale = (double)size / max;
|
||
|
||
cairo_surface_t *s2 = cairo_surface_create_similar_image(
|
||
t, CAIRO_FORMAT_ARGB32, reply->width * scale, reply->height * scale);
|
||
free(reply);
|
||
|
||
if (cairo_surface_status(s2) != CAIRO_STATUS_SUCCESS) {
|
||
cairo_surface_destroy(t);
|
||
return NULL;
|
||
}
|
||
// Paint it in.
|
||
cairo_t *d = cairo_create(s2);
|
||
cairo_scale(d, scale, scale);
|
||
cairo_set_source_surface(d, t, 0, 0);
|
||
cairo_paint(d);
|
||
cairo_destroy(d);
|
||
|
||
cairo_surface_destroy(t);
|
||
return s2;
|
||
}
|
||
/**
|
||
* 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);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Quick function that tries to fix the size (for dpi calculation)
|
||
* when monitor is rotate. This assumes the density is kinda equal in both X/Y
|
||
* direction.
|
||
*/
|
||
static void x11_workarea_fix_rotation(workarea *w) {
|
||
double ratio_res = w->w / (double)w->h;
|
||
double ratio_size = w->mw / (double)w->mh;
|
||
|
||
if ((ratio_res < 1.0 && ratio_size > 1.0) ||
|
||
(ratio_res > 1.0 && ratio_size < 1.0)) {
|
||
// Oposite ratios, swap them.
|
||
int nh = w->mw;
|
||
w->mw = w->mh;
|
||
w->mh = nh;
|
||
}
|
||
}
|
||
/**
|
||
* 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;
|
||
x11_workarea_fix_rotation(retv);
|
||
|
||
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;
|
||
}
|
||
|
||
#if (((XCB_RANDR_MAJOR_VERSION >= RANDR_PREF_MAJOR_VERSION) && \
|
||
(XCB_RANDR_MINOR_VERSION >= RANDR_PREF_MINOR_VERSION)) || \
|
||
XCB_RANDR_MAJOR_VERSION > RANDR_PREF_MAJOR_VERSION)
|
||
/**
|
||
* @param mon The randr monitor to parse.
|
||
*
|
||
* Create monitor based on xrandr monitor id.
|
||
*
|
||
* @returns A workarea representing the monitor mon
|
||
*/
|
||
static workarea *
|
||
x11_get_monitor_from_randr_monitor(xcb_randr_monitor_info_t *mon) {
|
||
// Query to the name of the monitor.
|
||
xcb_generic_error_t *err;
|
||
xcb_get_atom_name_cookie_t anc =
|
||
xcb_get_atom_name(xcb->connection, mon->name);
|
||
xcb_get_atom_name_reply_t *atom_reply =
|
||
xcb_get_atom_name_reply(xcb->connection, anc, &err);
|
||
if (err != NULL) {
|
||
g_warning("Could not get RandR monitor name: X11 error code %d\n",
|
||
err->error_code);
|
||
free(err);
|
||
return NULL;
|
||
}
|
||
workarea *retv = g_malloc0(sizeof(workarea));
|
||
|
||
// Is primary monitor.
|
||
retv->primary = mon->primary;
|
||
|
||
// Position and size.
|
||
retv->x = mon->x;
|
||
retv->y = mon->y;
|
||
retv->w = mon->width;
|
||
retv->h = mon->height;
|
||
|
||
// Physical
|
||
retv->mw = mon->width_in_millimeters;
|
||
retv->mh = mon->height_in_millimeters;
|
||
x11_workarea_fix_rotation(retv);
|
||
|
||
// Name
|
||
retv->name =
|
||
g_strdup_printf("%.*s", xcb_get_atom_name_name_length(atom_reply),
|
||
xcb_get_atom_name_name(atom_reply));
|
||
|
||
// Free name atom.
|
||
free(atom_reply);
|
||
|
||
return retv;
|
||
}
|
||
#endif
|
||
|
||
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(void) {
|
||
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(void) {
|
||
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.");
|
||
|
||
g_debug("Randr XCB api version: %d.%d.", XCB_RANDR_MAJOR_VERSION,
|
||
XCB_RANDR_MINOR_VERSION);
|
||
#if (((XCB_RANDR_MAJOR_VERSION == RANDR_PREF_MAJOR_VERSION) && \
|
||
(XCB_RANDR_MINOR_VERSION >= RANDR_PREF_MINOR_VERSION)) || \
|
||
XCB_RANDR_MAJOR_VERSION > RANDR_PREF_MAJOR_VERSION)
|
||
xcb_randr_query_version_cookie_t cversion = xcb_randr_query_version(
|
||
xcb->connection, RANDR_PREF_MAJOR_VERSION, RANDR_PREF_MINOR_VERSION);
|
||
xcb_randr_query_version_reply_t *rversion =
|
||
xcb_randr_query_version_reply(xcb->connection, cversion, NULL);
|
||
if (rversion) {
|
||
g_debug("Found randr version: %d.%d", rversion->major_version,
|
||
rversion->minor_version);
|
||
// Check if we are 1.5 and up.
|
||
if (((rversion->major_version == RANDR_PREF_MAJOR_VERSION) &&
|
||
(rversion->minor_version >= RANDR_PREF_MINOR_VERSION)) ||
|
||
(rversion->major_version > RANDR_PREF_MAJOR_VERSION)) {
|
||
xcb_randr_get_monitors_cookie_t t =
|
||
xcb_randr_get_monitors(xcb->connection, xcb->screen->root, 1);
|
||
xcb_randr_get_monitors_reply_t *mreply =
|
||
xcb_randr_get_monitors_reply(xcb->connection, t, NULL);
|
||
if (mreply) {
|
||
xcb_randr_monitor_info_iterator_t iter =
|
||
xcb_randr_get_monitors_monitors_iterator(mreply);
|
||
while (iter.rem > 0) {
|
||
workarea *w = x11_get_monitor_from_randr_monitor(iter.data);
|
||
if (w) {
|
||
w->next = xcb->monitors;
|
||
xcb->monitors = w;
|
||
}
|
||
xcb_randr_monitor_info_next(&iter);
|
||
}
|
||
free(mreply);
|
||
}
|
||
}
|
||
free(rversion);
|
||
}
|
||
#endif
|
||
|
||
// If no monitors found.
|
||
if (xcb->monitors == NULL) {
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
// If exists, free primary output reply.
|
||
if (pc_rep) {
|
||
free(pc_rep);
|
||
}
|
||
free(res_reply);
|
||
}
|
||
|
||
// Number monitor
|
||
int index = 0;
|
||
for (workarea *iter = xcb->monitors; iter; iter = iter->next) {
|
||
iter->monitor_id = index++;
|
||
}
|
||
}
|
||
|
||
static void xcb_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");
|
||
}
|
||
}
|
||
|
||
static void xcb_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, ¤t_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) {
|
||
if (mon == NULL) {
|
||
g_error("%s: mon == NULL", __func__);
|
||
return;
|
||
}
|
||
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) {
|
||
if (mon == NULL) {
|
||
g_error("%s: mon == NULL", __func__);
|
||
return FALSE;
|
||
}
|
||
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;
|
||
if (mon == NULL) {
|
||
g_error("%s: mon == NULL", __func__);
|
||
return retv;
|
||
}
|
||
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;
|
||
if ((current_window_manager & WM_ROOT_WINDOW_OFFSET) ==
|
||
WM_ROOT_WINDOW_OFFSET) {
|
||
mon->x += r->x;
|
||
mon->y += r->y;
|
||
}
|
||
g_debug("mon pos: %d %d %d-%d", mon->x, mon->y, mon->w, mon->h);
|
||
} else if (mon_id == -4) {
|
||
g_debug("Find monitor at location: %d %d", t->dst_x, t->dst_y);
|
||
monitor_dimensions(t->dst_x, t->dst_y, mon);
|
||
g_debug("Monitor found pos: %d %d %d-%d", mon->x, mon->y, mon->w, mon->h);
|
||
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;
|
||
if (mon == NULL) {
|
||
g_error("%s: mon == NULL", __func__);
|
||
return FALSE;
|
||
}
|
||
g_debug("Monitor id: %d", mon_id);
|
||
// 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) {
|
||
g_debug("rofi on current monitor");
|
||
// 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, ¤t_desktop,
|
||
NULL)) {
|
||
g_debug("Found current desktop: %u", current_desktop);
|
||
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)) {
|
||
g_debug("Found %d number of desktops", vp.desktop_viewport_len);
|
||
if (current_desktop < vp.desktop_viewport_len) {
|
||
g_debug("Found viewport for desktop: %d %d",
|
||
vp.desktop_viewport[current_desktop].x,
|
||
vp.desktop_viewport[current_desktop].y);
|
||
monitor_dimensions(vp.desktop_viewport[current_desktop].x,
|
||
vp.desktop_viewport[current_desktop].y, mon);
|
||
g_debug("Found monitor @: %d %d %dx%d", mon->x, mon->y, mon->w,
|
||
mon->h);
|
||
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);
|
||
}
|
||
|
||
/** The cached monitor setup (mon_cache) is populated */
|
||
gboolean mon_set = FALSE;
|
||
/** cached monitor cache, to avoid multiple roundtrips to fetch this. */
|
||
workarea mon_cache = {
|
||
0,
|
||
};
|
||
static int xcb_display_monitor_active(workarea *mon) {
|
||
if (mon == NULL) {
|
||
g_error("%s: mon == NULL", __func__);
|
||
return FALSE;
|
||
}
|
||
g_debug("Monitor active");
|
||
if (mon_set) {
|
||
*mon = mon_cache;
|
||
return TRUE;
|
||
}
|
||
if (config.monitor != NULL) {
|
||
g_debug("Monitor lookup by name : %s", config.monitor);
|
||
for (workarea *iter = xcb->monitors; iter; iter = iter->next) {
|
||
if (g_strcmp0(config.monitor, iter->name) == 0) {
|
||
*mon = *iter;
|
||
mon_cache = *mon;
|
||
mon_set = TRUE;
|
||
return TRUE;
|
||
}
|
||
}
|
||
}
|
||
g_debug("Monitor lookup by name failed: %s", config.monitor);
|
||
// Grab primary.
|
||
if (g_strcmp0(config.monitor, "primary") == 0) {
|
||
for (workarea *iter = xcb->monitors; iter; iter = iter->next) {
|
||
if (iter->primary) {
|
||
*mon = *iter;
|
||
mon_cache = *mon;
|
||
mon_set = TRUE;
|
||
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)) {
|
||
mon_cache = *mon;
|
||
mon_set = TRUE;
|
||
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)) {
|
||
mon_cache = *mon;
|
||
mon_set = TRUE;
|
||
return TRUE;
|
||
}
|
||
g_warning("Failed to find selected monitor.");
|
||
} else {
|
||
int val = monitor_active_from_id(mon_id, mon);
|
||
mon_cache = *mon;
|
||
mon_set = TRUE;
|
||
return val;
|
||
}
|
||
}
|
||
}
|
||
// Fallback.
|
||
monitor_dimensions(0, 0, mon);
|
||
mon_cache = *mon;
|
||
mon_set = TRUE;
|
||
return FALSE;
|
||
}
|
||
|
||
static bool get_atom_name(xcb_connection_t *conn, xcb_atom_t atom, char **out) {
|
||
xcb_get_atom_name_cookie_t cookie;
|
||
xcb_get_atom_name_reply_t *reply;
|
||
int length;
|
||
char *name;
|
||
|
||
if (atom == 0) {
|
||
*out = NULL;
|
||
return true;
|
||
}
|
||
|
||
cookie = xcb_get_atom_name(conn, atom);
|
||
reply = xcb_get_atom_name_reply(conn, cookie, NULL);
|
||
if (!reply)
|
||
return false;
|
||
|
||
length = xcb_get_atom_name_name_length(reply);
|
||
name = xcb_get_atom_name_name(reply);
|
||
|
||
(*out) = g_strndup(name, length);
|
||
if (!(*out)) {
|
||
free(reply);
|
||
return false;
|
||
}
|
||
|
||
free(reply);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @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_debug("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 {
|
||
char *out = NULL;
|
||
if (get_atom_name(xcb->connection, xse->property, &out)) {
|
||
g_debug("rofi_view_paste: Got unknown atom: %s", out);
|
||
g_free(out);
|
||
} else {
|
||
g_debug("rofi_view_paste: Got unknown, unnamed: %s", out);
|
||
}
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
static void rofi_key_press_event_handler(xcb_key_press_event_t *xkpe,
|
||
RofiViewState *state) {
|
||
gchar *text;
|
||
g_log("IMDKit", G_LOG_LEVEL_DEBUG, "press handler");
|
||
|
||
xcb->last_timestamp = xkpe->time;
|
||
if (config.xserver_i300_workaround) {
|
||
text = nk_bindings_seat_handle_key_with_modmask(
|
||
xcb->bindings_seat, NULL, xkpe->state, xkpe->detail,
|
||
NK_BINDINGS_KEY_STATE_PRESS);
|
||
} else {
|
||
text = nk_bindings_seat_handle_key(xcb->bindings_seat, NULL, xkpe->detail,
|
||
NK_BINDINGS_KEY_STATE_PRESS);
|
||
}
|
||
if (text != NULL) {
|
||
rofi_view_handle_text(state, text);
|
||
g_free(text);
|
||
}
|
||
}
|
||
|
||
static void rofi_key_release_event_handler(xcb_key_release_event_t *xkre,
|
||
G_GNUC_UNUSED RofiViewState *state) {
|
||
xcb->last_timestamp = xkre->time;
|
||
nk_bindings_seat_handle_key(xcb->bindings_seat, NULL, xkre->detail,
|
||
NK_BINDINGS_KEY_STATE_RELEASE);
|
||
}
|
||
|
||
/**
|
||
* 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_CLIENT_MESSAGE: {
|
||
xcb_client_message_event_t *cme = (xcb_client_message_event_t *)event;
|
||
xcb_atom_t atom = cme->data.data32[0];
|
||
xcb_timestamp_t time = cme->data.data32[1];
|
||
if (atom == netatoms[WM_TAKE_FOCUS]) {
|
||
xcb_set_input_focus(xcb->connection, XCB_INPUT_FOCUS_NONE, cme->window,
|
||
time);
|
||
xcb_flush(xcb->connection);
|
||
}
|
||
break;
|
||
}
|
||
case XCB_DESTROY_NOTIFY: {
|
||
xcb_window_t win = ((xcb_destroy_notify_event_t *)event)->window;
|
||
if (win != rofi_view_get_window()) {
|
||
#ifdef WINDOW_MODE
|
||
window_client_handle_signal(win, FALSE);
|
||
#endif
|
||
} else {
|
||
g_main_loop_quit(xcb->main_loop);
|
||
}
|
||
break;
|
||
}
|
||
case XCB_CREATE_NOTIFY: {
|
||
xcb_window_t win = ((xcb_create_notify_event_t *)event)->window;
|
||
if (win != rofi_view_get_window()) {
|
||
#ifdef WINDOW_MODE
|
||
window_client_handle_signal(win, TRUE);
|
||
#endif
|
||
}
|
||
break;
|
||
}
|
||
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: {
|
||
xcb_motion_notify_event_t *xme = (xcb_motion_notify_event_t *)event;
|
||
gboolean button_mask = xme->state & XCB_EVENT_MASK_BUTTON_1_MOTION;
|
||
rofi_view_handle_mouse_motion(state, xme->event_x, xme->event_y,
|
||
!button_mask && config.hover_select);
|
||
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, FALSE);
|
||
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);
|
||
}
|
||
xcb->mouse_seen++;
|
||
break;
|
||
}
|
||
case XCB_SELECTION_CLEAR: {
|
||
g_debug("Selection Clear.");
|
||
xcb_stuff_set_clipboard(NULL);
|
||
} break;
|
||
case XCB_SELECTION_REQUEST: {
|
||
g_debug("Selection Request.");
|
||
xcb_selection_request_event_t *req = (xcb_selection_request_event_t *)event;
|
||
if (req->selection == netatoms[CLIPBOARD]) {
|
||
xcb_atom_t targets[2];
|
||
xcb_selection_notify_event_t selection_notify = {
|
||
.response_type = XCB_SELECTION_NOTIFY,
|
||
.sequence = 0,
|
||
.time = req->time,
|
||
.requestor = req->requestor,
|
||
.selection = req->selection,
|
||
.target = req->target,
|
||
.property = XCB_ATOM_NONE,
|
||
};
|
||
// If no clipboard, we return NONE.
|
||
if (xcb->clipboard) {
|
||
// Request for UTF-8
|
||
if (req->target == netatoms[UTF8_STRING]) {
|
||
g_debug("Selection Request UTF-8.");
|
||
xcb_change_property(xcb->connection, XCB_PROP_MODE_REPLACE,
|
||
req->requestor, req->property,
|
||
netatoms[UTF8_STRING], 8,
|
||
strlen(xcb->clipboard) + 1, xcb->clipboard);
|
||
selection_notify.property = req->property;
|
||
} else if (req->target == netatoms[TARGETS]) {
|
||
g_debug("Selection Request Targets.");
|
||
// We currently only support UTF8 from clipboard. So indicate this.
|
||
targets[0] = netatoms[UTF8_STRING];
|
||
xcb_change_property(xcb->connection, XCB_PROP_MODE_REPLACE,
|
||
req->requestor, req->property, XCB_ATOM_ATOM, 32,
|
||
1, targets);
|
||
selection_notify.property = req->property;
|
||
}
|
||
}
|
||
|
||
xcb_send_event(xcb->connection,
|
||
0, // propagate
|
||
req->requestor, XCB_EVENT_MASK_NO_EVENT,
|
||
(const char *)&selection_notify);
|
||
xcb_flush(xcb->connection);
|
||
}
|
||
} 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--;
|
||
}
|
||
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;
|
||
#ifdef XCB_IMDKIT
|
||
if (xcb->ic) {
|
||
g_log("IMDKit", G_LOG_LEVEL_DEBUG, "input xim");
|
||
xcb_xim_forward_event(xcb->im, xcb->ic, xkpe);
|
||
} else
|
||
#endif
|
||
{
|
||
rofi_key_press_event_handler(xkpe, state);
|
||
}
|
||
break;
|
||
}
|
||
case XCB_KEY_RELEASE: {
|
||
xcb_key_release_event_t *xkre = (xcb_key_release_event_t *)event;
|
||
#ifdef XCB_IMDKIT
|
||
if (xcb->ic) {
|
||
xcb_xim_forward_event(xcb->im, xcb->ic, xkre);
|
||
} else
|
||
#endif
|
||
{
|
||
rofi_key_release_event_handler(xkre, state);
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
rofi_view_maybe_update(state);
|
||
}
|
||
|
||
#ifdef XCB_IMDKIT
|
||
void x11_event_handler_fowarding(G_GNUC_UNUSED xcb_xim_t *im,
|
||
G_GNUC_UNUSED xcb_xic_t ic,
|
||
xcb_key_press_event_t *event,
|
||
G_GNUC_UNUSED void *user_data) {
|
||
RofiViewState *state = rofi_view_get_active();
|
||
if (state == NULL) {
|
||
return;
|
||
}
|
||
uint8_t type = event->response_type & ~0x80;
|
||
if (type == XCB_KEY_PRESS) {
|
||
rofi_key_press_event_handler(event, state);
|
||
} else if (type == XCB_KEY_RELEASE) {
|
||
xcb_key_release_event_t *xkre = (xcb_key_release_event_t *)event;
|
||
rofi_key_release_event_handler(xkre, state);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
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;
|
||
}
|
||
// DD: it seems this handler often gets dispatched while the queue in GWater
|
||
// is empty. resulting in a NULL for ev. This seems not an error.
|
||
// g_warning("main_loop_x11_event_handler: ev == NULL, status == %d",
|
||
// status);
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
|
||
#ifdef XCB_IMDKIT
|
||
if (xcb->im && xcb_xim_filter_event(xcb->im, ev)) {
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
#endif
|
||
|
||
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;
|
||
}
|
||
|
||
void rofi_xcb_set_input_focus(xcb_window_t w) {
|
||
if (config.steal_focus != TRUE) {
|
||
xcb->focus_revert = 0;
|
||
return;
|
||
}
|
||
xcb_generic_error_t *error;
|
||
xcb_get_input_focus_reply_t *freply;
|
||
xcb_get_input_focus_cookie_t fcookie = xcb_get_input_focus(xcb->connection);
|
||
freply = xcb_get_input_focus_reply(xcb->connection, fcookie, &error);
|
||
if (error != NULL) {
|
||
g_warning("Could not get input focus (error %d), will revert focus to best "
|
||
"effort",
|
||
error->error_code);
|
||
free(error);
|
||
xcb->focus_revert = 0;
|
||
} else {
|
||
xcb->focus_revert = freply->focus;
|
||
}
|
||
xcb_set_input_focus(xcb->connection, XCB_INPUT_FOCUS_POINTER_ROOT, w,
|
||
XCB_CURRENT_TIME);
|
||
xcb_flush(xcb->connection);
|
||
}
|
||
|
||
void rofi_xcb_revert_input_focus(void) {
|
||
if (xcb->focus_revert == 0) {
|
||
return;
|
||
}
|
||
|
||
xcb_set_input_focus(xcb->connection, XCB_INPUT_FOCUS_POINTER_ROOT,
|
||
xcb->focus_revert, XCB_CURRENT_TIME);
|
||
xcb_flush(xcb->connection);
|
||
}
|
||
|
||
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;
|
||
}
|
||
struct timespec del = {.tv_sec = 0, .tv_nsec = 1000000};
|
||
nanosleep(&del, NULL);
|
||
}
|
||
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;
|
||
}
|
||
struct timespec del = {.tv_sec = 0, .tv_nsec = 1000000};
|
||
nanosleep(&del, NULL);
|
||
}
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
char *x11_helper_get_window_manager(void) {
|
||
char *retv = NULL;
|
||
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) {
|
||
retv = g_strndup(wtitle.strings, wtitle.strings_len);
|
||
}
|
||
xcb_ewmh_get_utf8_strings_reply_wipe(&wtitle);
|
||
}
|
||
}
|
||
return retv;
|
||
}
|
||
|
||
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) {
|
||
// Copy the string and add terminating '\0'.
|
||
char *str = g_strndup(wtitle.strings, wtitle.strings_len);
|
||
g_debug("Found window manager: |%s|", str);
|
||
if (g_strcmp0(str, "i3") == 0) {
|
||
current_window_manager =
|
||
WM_DO_NOT_CHANGE_CURRENT_DESKTOP | WM_PANGO_WORKSPACE_NAMES;
|
||
} else if (g_strcmp0(str, "bspwm") == 0) {
|
||
current_window_manager = WM_ROOT_WINDOW_OFFSET;
|
||
}
|
||
g_free(str);
|
||
}
|
||
xcb_ewmh_get_utf8_strings_reply_wipe(&wtitle);
|
||
}
|
||
}
|
||
}
|
||
|
||
static gboolean xcb_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;
|
||
#ifdef XCB_IMDKIT
|
||
xcb_compound_text_init();
|
||
#endif
|
||
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);
|
||
#ifdef XCB_IMDKIT
|
||
xcb->im = xcb_xim_create(xcb->connection, xcb->screen_nbr, NULL);
|
||
#endif
|
||
|
||
#ifdef XCB_IMDKIT
|
||
#ifndef XCB_IMDKIT_1_0_3_LOWER
|
||
xcb_xim_set_use_compound_text(xcb->im, true);
|
||
xcb_xim_set_use_utf8_string(xcb->im, true);
|
||
#endif
|
||
#endif
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
static void x11_lookup_cursors(void) {
|
||
xcb_cursor_context_t *ctx;
|
||
|
||
if (xcb_cursor_context_new(xcb->connection, xcb->screen, &ctx) < 0) {
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < NUM_CURSORS; ++i) {
|
||
cursors[i] = xcb_cursor_load_cursor(ctx, cursor_names[i].css_name);
|
||
|
||
if (cursors[i] == XCB_CURSOR_NONE) {
|
||
cursors[i] =
|
||
xcb_cursor_load_cursor(ctx, cursor_names[i].traditional_name);
|
||
}
|
||
}
|
||
|
||
xcb_cursor_context_free(ctx);
|
||
}
|
||
|
||
/** 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;
|
||
}
|
||
|
||
static gboolean xcb_display_late_setup(void) {
|
||
x11_create_visual_and_colormap();
|
||
|
||
x11_lookup_cursors();
|
||
|
||
/**
|
||
* 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; }
|
||
|
||
static void xcb_display_early_cleanup(void) {
|
||
release_keyboard();
|
||
release_pointer();
|
||
xcb_flush(xcb->connection);
|
||
}
|
||
|
||
static void xcb_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);
|
||
#ifdef XCB_IMDKIT
|
||
xcb_xim_close(xcb->im);
|
||
xcb_xim_destroy(xcb->im);
|
||
xcb->im = NULL;
|
||
#endif
|
||
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);
|
||
}
|
||
|
||
void x11_set_cursor(xcb_window_t window, X11CursorType type) {
|
||
if (type < 0 || type >= NUM_CURSORS) {
|
||
return;
|
||
}
|
||
|
||
if (cursors[type] == XCB_CURSOR_NONE) {
|
||
return;
|
||
}
|
||
|
||
xcb_change_window_attributes(xcb->connection, window, XCB_CW_CURSOR,
|
||
&(cursors[type]));
|
||
}
|
||
void xcb_stuff_set_clipboard(char *data) {
|
||
g_free(xcb->clipboard);
|
||
xcb->clipboard = data;
|
||
}
|
||
|
||
static void xcb_display_set_input_focus(guint w) {
|
||
if (config.steal_focus != TRUE) {
|
||
xcb->focus_revert = 0;
|
||
return;
|
||
}
|
||
xcb_generic_error_t *error;
|
||
xcb_get_input_focus_reply_t *freply;
|
||
xcb_get_input_focus_cookie_t fcookie = xcb_get_input_focus(xcb->connection);
|
||
freply = xcb_get_input_focus_reply(xcb->connection, fcookie, &error);
|
||
if (error != NULL) {
|
||
g_warning("Could not get input focus (error %d), will revert focus to best "
|
||
"effort",
|
||
error->error_code);
|
||
free(error);
|
||
xcb->focus_revert = 0;
|
||
} else {
|
||
xcb->focus_revert = freply->focus;
|
||
}
|
||
xcb_set_input_focus(xcb->connection, XCB_INPUT_FOCUS_POINTER_ROOT, w,
|
||
XCB_CURRENT_TIME);
|
||
xcb_flush(xcb->connection);
|
||
}
|
||
|
||
static void xcb_display_revert_input_focus(void) {
|
||
if (xcb->focus_revert == 0) {
|
||
return;
|
||
}
|
||
|
||
xcb_set_input_focus(xcb->connection, XCB_INPUT_FOCUS_POINTER_ROOT,
|
||
xcb->focus_revert, XCB_CURRENT_TIME);
|
||
xcb_flush(xcb->connection);
|
||
}
|
||
|
||
static guint xcb_display_scale(void) { return 1; }
|
||
|
||
static const struct _view_proxy *xcb_display_view_proxy(void) {
|
||
return xcb_view_proxy;
|
||
}
|
||
|
||
static display_proxy display_ = {
|
||
.setup = xcb_display_setup,
|
||
.late_setup = xcb_display_late_setup,
|
||
.early_cleanup = xcb_display_early_cleanup,
|
||
.cleanup = xcb_display_cleanup,
|
||
.dump_monitor_layout = xcb_display_dump_monitor_layout,
|
||
.startup_notification = xcb_display_startup_notification,
|
||
.monitor_active = xcb_display_monitor_active,
|
||
.set_input_focus = xcb_display_set_input_focus,
|
||
.revert_input_focus = xcb_display_revert_input_focus,
|
||
.scale = xcb_display_scale,
|
||
|
||
.view = xcb_display_view_proxy,
|
||
};
|
||
|
||
display_proxy *const xcb_proxy = &display_;
|