From 37ecb4b496815115049a1844a149d6bf63811a9c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 15 Dec 2022 10:53:31 +0000 Subject: [PATCH] core: detect screen off Use the DPMS extension to detect if screen is turned off, and unredirect if it is. This also helps working around the problem where OpenGL buffers lose data when screen is turned off, causing screen to flicker later when it turns back on if use-damage is enabled. Unfortunately the DPMS extension doesn't define an event, so we have to periodically poll the screen state. Signed-off-by: Yuxuan Shui --- .github/workflows/codeql-analysis.yml | 2 +- README.md | 3 +- src/common.h | 6 ++++ src/meson.build | 3 +- src/picom.c | 51 +++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8b65b61c..224c4628 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -36,7 +36,7 @@ jobs: languages: ${{ matrix.language }} # Install dependencies - - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build + - run: sudo apt update && sudo apt install libxext-dev libxcb1-dev libxcb-dpms0-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ninja-build if: ${{ matrix.language == 'cpp' }} # Autobuild diff --git a/README.md b/README.md index f6bdfe18..a23dfd05 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth * xproto * xcb * xcb-damage +* xcb-dpms * xcb-xfixes * xcb-shape * xcb-renderutil @@ -44,7 +45,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth On Debian based distributions (e.g. Ubuntu), the needed packages are ``` -libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson +libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl-dev libegl-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev meson ``` On Fedora, the needed packages are diff --git a/src/common.h b/src/common.h index 542dc947..2e5495e6 100644 --- a/src/common.h +++ b/src/common.h @@ -150,6 +150,8 @@ typedef struct session { // === Event handlers === /// ev_io for X connection ev_io xiow; + /// Timer for checking DPMS power level + ev_timer dpms_check_timer; /// Timeout for delayed unredirection. ev_timer unredir_timer; /// Timer for fading @@ -236,6 +238,8 @@ typedef struct session { xcb_sync_fence_t sync_fence; /// Whether we are rendering the first frame after screen is redirected bool first_frame; + /// Whether screen has been turned off + bool screen_is_off; // === Operation related === /// Flags related to the root window @@ -342,6 +346,8 @@ typedef struct session { int composite_error; /// Major opcode for X Composite extension. int composite_opcode; + /// Whether X DPMS extension exists + bool dpms_exists; /// Whether X Shape extension exists. bool shape_exists; /// Event base number for X Shape extension. diff --git a/src/meson.build b/src/meson.build index 09eb07b6..b6b24b56 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,7 +16,8 @@ cflags = [] required_xcb_packages = [ 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite', - 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb' + 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', + 'xcb-dpms', 'xcb' ] required_packages = [ diff --git a/src/picom.c b/src/picom.c index ba97f4cc..b5ac3a34 100644 --- a/src/picom.c +++ b/src/picom.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,26 @@ void cxinerama_upd_scrs(session_t *ps) { free(xinerama_scrs); } +static inline bool dpms_screen_is_off(xcb_dpms_info_reply_t *info) { + // state is a bool indicating whether dpms is enabled + return info->state && (info->power_level != XCB_DPMS_DPMS_MODE_ON); +} + +void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { + auto ps = session_ptr(w, dpms_check_timer); + auto r = xcb_dpms_info_reply(ps->c, xcb_dpms_info(ps->c), NULL); + if (!r) { + log_fatal("Failed to query DPMS status."); + abort(); + } + auto now_screen_is_off = dpms_screen_is_off(r); + if (ps->screen_is_off != now_screen_is_off) { + ps->screen_is_off = now_screen_is_off; + queue_redraw(ps); + } + free(r); +} + /** * Find matched window. * @@ -920,6 +941,19 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation) { // If there's no window to paint, and the screen isn't redirected, // don't redirect it. unredir_possible = true; + } else if (ps->screen_is_off) { + // Screen is off, unredirect + // We do this unconditionally disregarding "unredir_if_possible" + // because it's important for correctness, because we need to + // workaround problems X server has around screen off. + // + // Known problems: + // 1. Sometimes OpenGL front buffer can lose content, and if we + // are doing partial updates (i.e. use-damage = true), the + // result will be wrong. + // 2. For frame pacing, X server sends bogus + // PresentCompleteNotify events when screen is off. + unredir_possible = true; } if (unredir_possible) { if (ps->redirected) { @@ -1800,6 +1834,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, xcb_prefetch_extension_data(ps->c, &xcb_present_id); xcb_prefetch_extension_data(ps->c, &xcb_sync_id); xcb_prefetch_extension_data(ps->c, &xcb_glx_id); + xcb_prefetch_extension_data(ps->c, &xcb_dpms_id); ext_info = xcb_get_extension_data(ps->c, &xcb_render_id); if (!ext_info || !ext_info->present) { @@ -1868,6 +1903,21 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->glx_event = ext_info->first_event; } + ext_info = xcb_get_extension_data(ps->c, &xcb_dpms_id); + ps->dpms_exists = ext_info && ext_info->present; + if (ps->dpms_exists) { + auto r = xcb_dpms_info_reply(ps->c, xcb_dpms_info(ps->c), NULL); + if (!r) { + log_fatal("Failed to query DPMS info"); + goto err; + } + ps->screen_is_off = dpms_screen_is_off(r); + // Check screen status every half second + ev_timer_init(&ps->dpms_check_timer, check_dpms_status, 0, 0.5); + ev_timer_start(ps->loop, &ps->dpms_check_timer); + free(r); + } + // Parse configuration file win_option_mask_t winopt_mask[NUM_WINTYPES] = {{0}}; bool shadow_enabled = false, fading_enable = false, hasneg = false; @@ -2462,6 +2512,7 @@ static void session_destroy(session_t *ps) { // Stop libev event handlers ev_timer_stop(ps->loop, &ps->unredir_timer); ev_timer_stop(ps->loop, &ps->fade_timer); + ev_timer_stop(ps->loop, &ps->dpms_check_timer); ev_idle_stop(ps->loop, &ps->draw_idle); ev_prepare_stop(ps->loop, &ps->event_check); ev_signal_stop(ps->loop, &ps->usr1_signal);