Add prototype picom-inspect

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-02-20 00:34:26 +00:00
parent 4954c11aae
commit 6a01cec68a
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
8 changed files with 338 additions and 34 deletions

View File

@ -1518,6 +1518,10 @@ static const char *c2_condition_to_str2(c2_ptr_t ptr) {
return buf;
}
const char *c2_lptr_to_str(const c2_lptr_t *ptr) {
return c2_condition_to_str2(ptr->ptr);
}
/// Get the list of target number values from a struct c2_property_value
static inline const int64_t *
c2_values_get_number_targets(const struct c2_property_value *values, int index, size_t *n) {

View File

@ -57,6 +57,9 @@ typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data);
bool c2_list_foreach(const c2_lptr_t *list, c2_list_foreach_cb_t cb, void *data);
/// Return user data stored in a condition.
void *c2_list_get_data(const c2_lptr_t *condlist);
/// Convert a c2_lptr_t to string. The returned string is only valid until the
/// next call to this function, and should not be freed.
const char *c2_lptr_to_str(const c2_lptr_t *);
/**
* Destroy a condition list.

272
src/inspect.c Normal file
View File

@ -0,0 +1,272 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2024 Yuxuan Shui <yshuiv7@gmail.com>
#include <X11/Xlib.h>
#include <stddef.h>
#include <stdint.h>
#include <xcb/shape.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include <xcb/xproto.h>
#include "inspect.h"
#include "atom.h"
#include "c2.h"
#include "common.h"
#include "config.h"
#include "err.h"
#include "log.h"
#include "options.h"
#include "utils.h"
#include "win.h"
#include "win_defs.h"
#include "x.h"
static struct managed_win *
setup_window(struct x_connection *c, struct atom *atoms, struct options *options,
struct c2_state *state, xcb_window_t target) {
// Pretend we are the compositor, and build up the window state
struct managed_win *w = ccalloc(1, struct managed_win);
w->state = WSTATE_MAPPED;
w->base.id = target;
w->client_win = win_get_client_window(c, atoms, w);
win_update_wintype(c, atoms, w);
win_update_frame_extents(c, atoms, w, w->client_win, options->frame_opacity);
// TODO(yshui) get leader
win_update_name(c, atoms, w);
win_update_class(c, atoms, w);
win_update_role(c, atoms, w);
auto geometry_reply = XCB_AWAIT(xcb_get_geometry, c->c, w->base.id);
w->g = (struct win_geometry){
.x = geometry_reply->x,
.y = geometry_reply->y,
.width = geometry_reply->width,
.height = geometry_reply->height,
};
free(geometry_reply);
auto shape_info = xcb_get_extension_data(c->c, &xcb_shape_id);
win_on_win_size_change(w, options->shadow_offset_x, options->shadow_offset_y,
options->shadow_radius);
win_update_bounding_shape(c, w, shape_info->present, options->detect_rounded_corners);
win_update_prop_fullscreen(c, atoms, w);
// Determine if the window is focused
xcb_window_t wid = XCB_NONE;
if (options->use_ewmh_active_win) {
wid_get_prop_window(c, c->screen_info->root, atoms->a_NET_ACTIVE_WINDOW);
} else {
// Determine the currently focused window so we can apply appropriate
// opacity on it
xcb_get_input_focus_reply_t *reply =
xcb_get_input_focus_reply(c->c, xcb_get_input_focus(c->c), NULL);
if (reply) {
wid = reply->focus;
free(reply);
}
}
if (wid == w->base.id || wid == w->client_win) {
w->focused = true;
}
auto attributes_reply = XCB_AWAIT(xcb_get_window_attributes, c->c, w->base.id);
w->a = *attributes_reply;
w->pictfmt = x_get_pictform_for_visual(c, w->a.visual);
free(attributes_reply);
c2_window_state_init(state, &w->c2_state);
c2_window_state_update(state, &w->c2_state, c->c, w->client_win, w->base.id);
return w;
}
xcb_window_t select_window(struct x_connection *c) {
xcb_font_t font = x_new_id(c);
xcb_cursor_t cursor = x_new_id(c);
const char font_name[] = "cursor";
static const uint16_t CROSSHAIR_CHAR = 34;
XCB_AWAIT_VOID(xcb_open_font, c->c, font, sizeof(font_name) - 1, font_name);
XCB_AWAIT_VOID(xcb_create_glyph_cursor, c->c, cursor, font, font, CROSSHAIR_CHAR,
CROSSHAIR_CHAR + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
auto grab_reply = XCB_AWAIT(
xcb_grab_pointer, c->c, false, c->screen_info->root,
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_SYNC,
XCB_GRAB_MODE_ASYNC, c->screen_info->root, cursor, XCB_CURRENT_TIME);
if (grab_reply->status != XCB_GRAB_STATUS_SUCCESS) {
log_fatal("Failed to grab pointer");
return 1;
}
free(grab_reply);
// Let the user pick a window by clicking on it, mostly stolen from
// xprop
xcb_window_t target = XCB_NONE;
int buttons_pressed = 0;
while ((target == XCB_NONE) || (buttons_pressed > 0)) {
XCB_AWAIT_VOID(xcb_allow_events, c->c, XCB_ALLOW_ASYNC_POINTER,
XCB_CURRENT_TIME);
xcb_generic_event_t *ev = xcb_wait_for_event(c->c);
if (!ev) {
log_fatal("Connection to X server lost");
return 1;
}
switch (XCB_EVENT_RESPONSE_TYPE(ev)) {
case XCB_BUTTON_PRESS: {
xcb_button_press_event_t *e = (xcb_button_press_event_t *)ev;
if (target == XCB_NONE) {
target = e->child;
if (target == XCB_NONE) {
target = e->root;
}
}
buttons_pressed++;
break;
}
case XCB_BUTTON_RELEASE: {
if (buttons_pressed > 0) {
buttons_pressed--;
}
break;
}
default: break;
}
free(ev);
}
XCB_AWAIT_VOID(xcb_ungrab_pointer, c->c, XCB_CURRENT_TIME);
return target;
}
struct c2_match_state {
struct c2_state *state;
struct managed_win *w;
bool print_value;
};
bool c2_match_once_and_log(const c2_lptr_t *cond, void *data) {
struct c2_match_state *state = data;
void *rule_data = NULL;
printf(" %s ... ", c2_lptr_to_str(cond));
bool matched = c2_match(state->state, state->w, cond, &rule_data);
printf("%s", matched ? "\033[1;32mmatched\033[0m" : "not matched");
if (state->print_value && matched) {
printf("/%lu", (unsigned long)(intptr_t)rule_data);
state->print_value = false;
}
printf("\n");
return false;
}
#define BOLD(str) "\033[1m" str "\033[0m"
int inspect_main(int argc, char **argv, const char *config_file) {
auto stderr_logger = stderr_logger_new();
if (stderr_logger) {
log_add_target_tls(stderr_logger);
}
Display *dpy = XOpenDisplay(NULL);
if (!dpy) {
log_fatal("Can't open display");
return 1;
}
struct x_connection c;
x_connection_init(&c, dpy);
xcb_prefetch_extension_data(c.c, &xcb_shape_id);
char *config_file_to_free = NULL;
struct options options;
bool shadow_enabled, fading_enable, hasneg;
win_option_mask_t winopt_mask[NUM_WINTYPES] = {0};
config_file = config_file_to_free = parse_config(
&options, config_file, &shadow_enabled, &fading_enable, &hasneg, winopt_mask);
if (IS_ERR(config_file_to_free)) {
return 1;
}
// Parse all of the rest command line options
if (!get_cfg(&options, argc, argv, shadow_enabled, fading_enable, hasneg, winopt_mask)) {
log_fatal("Failed to get configuration, usually mean you have specified "
"invalid options.");
return 1;
}
auto atoms attr_unused = init_atoms(c.c);
auto state = c2_state_new(atoms);
options_postprocess_c2_lists(state, &c, &options);
auto target = select_window(&c);
log_info("Target window: %#x", target);
auto w = setup_window(&c, atoms, &options, state, target);
struct c2_match_state match_state = {
.state = state,
.w = w,
};
printf("Checking " BOLD("transparent-clipping-exclude") ":\n");
c2_list_foreach(options.transparent_clipping_blacklist, c2_match_once_and_log,
&match_state);
printf("Checking " BOLD("shadow-exclude") ":\n");
c2_list_foreach(options.shadow_blacklist, c2_match_once_and_log, &match_state);
printf("Checking " BOLD("fade-exclude") ":\n");
c2_list_foreach(options.fade_blacklist, c2_match_once_and_log, &match_state);
printf("Checking " BOLD("clip-shadow-above") ":\n");
c2_list_foreach(options.shadow_clip_list, c2_match_once_and_log, &match_state);
printf("Checking " BOLD("focus-exclude") ":\n");
c2_list_foreach(options.focus_blacklist, c2_match_once_and_log, &match_state);
printf("Checking " BOLD("invert-color-include") ":\n");
c2_list_foreach(options.invert_color_list, c2_match_once_and_log, &match_state);
printf("Checking " BOLD("blur-background-exclude") ":\n");
c2_list_foreach(options.blur_background_blacklist, c2_match_once_and_log, &match_state);
printf("Checking " BOLD("unredir-if-possible-exclude") ":\n");
c2_list_foreach(options.unredir_if_possible_blacklist, c2_match_once_and_log,
&match_state);
printf("Checking " BOLD("rounded-corners-exclude") ":\n");
c2_list_foreach(options.rounded_corners_blacklist, c2_match_once_and_log, &match_state);
match_state.print_value = true;
printf("Checking " BOLD("opacity-rule") ":\n");
c2_list_foreach(options.opacity_rules, c2_match_once_and_log, &match_state);
printf("Checking " BOLD("corner-radius-rule") ":\n");
c2_list_foreach(options.corner_radius_rules, c2_match_once_and_log, &match_state);
printf("\nHere are some rule(s) that match this window:\n");
if (w->name != NULL) {
printf(" name = '%s'\n", w->name);
}
if (w->class_instance != NULL) {
printf(" class_i = '%s'\n", w->class_instance);
}
if (w->class_general != NULL) {
printf(" class_g = '%s'\n", w->class_general);
}
if (w->role != NULL) {
printf(" role = '%s'\n", w->role);
}
if (w->window_type != WINTYPE_UNKNOWN) {
printf(" window_type = '%s'\n", WINTYPES[w->window_type].name);
}
printf(" %sfullscreen\n", w->is_fullscreen ? "" : "! ");
if (w->bounding_shaped) {
printf(" bounding_shaped\n");
}
printf(" border_width = %d\n", w->g.border_width);
pixman_region32_fini(&w->bounding_shape);
free(w->name);
free(w->class_instance);
free(w->class_general);
free(w->role);
c2_window_state_destroy(state, &w->c2_state);
free(w);
log_deinit_tls();
free(config_file_to_free);
c2_state_free(state);
destroy_atoms(atoms);
options_destroy(&options);
XCloseDisplay(c.dpy);
return 0;
}

15
src/inspect.h Normal file
View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2024 Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <xcb/xcb.h>
#include "compiler.h"
#ifdef CONFIG_LIBCONFIG
int inspect_main(int argc, char **argv, const char *config_file);
#else
static inline int inspect_main(int argc attr_unused, char **argv attr_unused,
const char *config_file attr_unused) {
return 0;
}
#endif

View File

@ -44,7 +44,7 @@ if get_option('config_file')
deps += [dependency('libconfig', version: '>=1.4', required: true)]
cflags += ['-DCONFIG_LIBCONFIG']
srcs += [ 'config_libconfig.c' ]
srcs += [ 'config_libconfig.c', 'inspect.c' ]
endif
if get_option('regex')
pcre = dependency('libpcre2-8', required: true)
@ -96,6 +96,10 @@ if get_option('unittest')
test('picom unittest', picom, args: [ '--unittest' ])
endif
if get_option('config_file')
install_symlink('picom-inspect', install_dir: 'bin', pointing_to: 'picom')
endif
if cc.has_argument('-fsanitize=fuzzer')
c2_fuzz = executable('c2_fuzz', srcs + ['fuzzer/c2.c'],
c_args: cflags + ['-fsanitize=fuzzer', '-DCONFIG_FUZZER'],

View File

@ -18,6 +18,7 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libgen.h>
#include <math.h>
#include <sched.h>
#include <stddef.h>
@ -44,6 +45,7 @@
#include "compiler.h"
#include "config.h"
#include "err.h"
#include "inspect.h"
#include "kernel.h"
#include "picom.h"
#include "win_defs.h"
@ -2781,6 +2783,11 @@ int PICOM_MAIN(int argc, char **argv) {
return exit_code;
}
char *exe_name = basename(argv[0]);
if (strcmp(exe_name, "picom-inspect") == 0) {
return inspect_main(argc, argv, config_file);
}
int pfds[2];
if (need_fork) {
if (pipe2(pfds, O_CLOEXEC)) {

View File

@ -53,28 +53,12 @@ static const int WIN_GET_LEADER_MAX_RECURSION = 20;
static const int ROUNDED_PIXELS = 1;
static const double ROUNDED_PERCENT = 0.05;
/**
* Retrieve the <code>WM_CLASS</code> of a window and update its
* <code>win</code> structure.
*/
static bool
win_update_class(struct x_connection *c, struct atom *atoms, struct managed_win *w);
static int win_update_role(struct x_connection *c, struct atom *atoms, struct managed_win *w);
static bool
win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w);
static int win_update_name(struct x_connection *c, struct atom *atoms, struct managed_win *w);
/**
* Reread opacity property of a window.
*/
static void win_update_opacity_prop(struct x_connection *c, struct atom *atoms,
struct managed_win *w, bool detect_client_opacity);
static void win_update_opacity_target(session_t *ps, struct managed_win *w);
/**
* Retrieve frame extents from a window.
*/
static void
win_update_frame_extents(struct x_connection *c, struct atom *atoms,
struct managed_win *w, xcb_window_t client, double frame_opacity);
static void win_update_prop_shadow_raw(struct x_connection *c, struct atom *atoms,
struct managed_win *w);
static bool
@ -90,13 +74,7 @@ bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms
static xcb_window_t
win_get_leader_property(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
bool detect_transient, bool detect_client_leader);
static xcb_window_t win_get_client_window(struct x_connection *c, struct atom *atoms,
const struct managed_win *w);
static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client);
static void win_on_win_size_change(struct managed_win *w, int shadow_offset_x,
int shadow_offset_y, int shadow_radius);
static void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
bool shape_exists, bool detect_rounded_corners);
/// Generate a "no corners" region function, from a function that returns the
/// region via a region_t pointer argument. Corners of the window will be removed from
@ -735,7 +713,7 @@ int win_update_name(struct x_connection *c, struct atom *atoms, struct managed_w
return ret;
}
static int win_update_role(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
int win_update_role(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
char **strlst = NULL;
int nstr = 0;
@ -1319,8 +1297,8 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
/**
* Update cache data in struct _win that depends on window size.
*/
static void win_on_win_size_change(struct managed_win *w, int shadow_offset_x,
int shadow_offset_y, int shadow_radius) {
void win_on_win_size_change(struct managed_win *w, int shadow_offset_x,
int shadow_offset_y, int shadow_radius) {
log_trace("Window %#010x (%s) size changed, was %dx%d, now %dx%d", w->base.id,
w->name, w->widthb, w->heightb, w->g.width + w->g.border_width * 2,
w->g.height + w->g.border_width * 2);
@ -1341,8 +1319,7 @@ static void win_on_win_size_change(struct managed_win *w, int shadow_offset_x,
/**
* Update window type.
*/
static bool
win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
const wintype_t wtype_old = w->window_type;
// Detect window type here
@ -1478,8 +1455,8 @@ find_client_win(struct x_connection *c, struct atom *atoms, xcb_window_t w) {
* @param ps current session
* @param w struct _win of the parent window
*/
static xcb_window_t win_get_client_window(struct x_connection *c, struct atom *atoms,
const struct managed_win *w) {
xcb_window_t win_get_client_window(struct x_connection *c, struct atom *atoms,
const struct managed_win *w) {
// Always recursively look for a window with WM_STATE, as Fluxbox
// sets override-redirect flags on all frame windows.
xcb_window_t cw = find_client_win(c, atoms, w->base.id);
@ -1844,8 +1821,7 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int
* Retrieve the <code>WM_CLASS</code> of a window and update its
* <code>win</code> structure.
*/
static bool
win_update_class(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
bool win_update_class(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
char **strlst = NULL;
int nstr = 0;
@ -1972,8 +1948,8 @@ gen_by_val(win_extents);
*
* Mark the window shape as updated
*/
static void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
bool shape_exists, bool detect_rounded_corners) {
void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
bool shape_exists, bool detect_rounded_corners) {
// We don't handle property updates of non-visible windows until they are
// mapped.
assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&

View File

@ -457,6 +457,29 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags);
/// Mark properties as stale for a window
void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops);
xcb_window_t win_get_client_window(struct x_connection *c, struct atom *atoms,
const struct managed_win *w);
bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w);
/**
* Retrieve frame extents from a window.
*/
void win_update_frame_extents(struct x_connection *c, struct atom *atoms,
struct managed_win *w, xcb_window_t client,
double frame_opacity);
/**
* Retrieve the <code>WM_CLASS</code> of a window and update its
* <code>win</code> structure.
*/
bool win_update_class(struct x_connection *c, struct atom *atoms, struct managed_win *w);
int win_update_role(struct x_connection *c, struct atom *atoms, struct managed_win *w);
int win_update_name(struct x_connection *c, struct atom *atoms, struct managed_win *w);
void win_on_win_size_change(struct managed_win *w, int shadow_offset_x,
int shadow_offset_y, int shadow_radius);
void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
bool shape_exists, bool detect_rounded_corners);
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
struct managed_win *w);
static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb_atom_t prop) {
return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1);
}