Skip to content

Commit

Permalink
Add prototype picom-inspect
Browse files Browse the repository at this point in the history
Signed-off-by: Yuxuan Shui <[email protected]>
  • Loading branch information
yshui committed Feb 20, 2024
1 parent 4954c11 commit 92ae24d
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 34 deletions.
4 changes: 4 additions & 0 deletions src/c2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions src/c2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
257 changes: 257 additions & 0 deletions src/inspect.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2024 Yuxuan Shui <[email protected]>
#include "inspect.h"
#include <stddef.h>
#include <stdint.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include "X11/Xlib.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"
#include "xcb/shape.h"
#include "xcb/xproto.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 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,
};
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);
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) {
log_init_tls();

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);

c2_state_free(state);
destroy_atoms(atoms);
options_destroy(&options);
XCloseDisplay(c.dpy);
return 0;
}
15 changes: 15 additions & 0 deletions src/inspect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2024 Yuxuan Shui <[email protected]>

#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
2 changes: 1 addition & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ base_deps = [
srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c',
'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c',
'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c',
'vblank.c') ]
'vblank.c', 'inspect.c') ]
picom_inc = include_directories('.')

cflags = []
Expand Down
6 changes: 6 additions & 0 deletions src/picom.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "compiler.h"
#include "config.h"
#include "err.h"
#include "inspect.h"
#include "kernel.h"
#include "picom.h"
#include "win_defs.h"
Expand Down Expand Up @@ -2781,6 +2782,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)) {
Expand Down
Loading

0 comments on commit 92ae24d

Please sign in to comment.