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