diff --git a/.gitignore b/.gitignore index 7b28f94552..0cac7892bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Build files .deps +.direnv aclocal.m4 autom4te.cache config.log diff --git a/CHANGELOG.md b/CHANGELOG.md index eb8bf7421e..a11085678b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Unreleased +## New features + +* Allow `corner-radius-rules` to override `corner-radius = 0`. Previously setting corner radius to 0 globally disables rounded corners. (#1170) + +## Bug fixes + +* Workaround a NVIDIA problem that causes high CPU usage after suspend/resume (#1172, #1168) + +# v11.1 (2024-Jan-28) + ## Bug fixes * Fix missing fading on window close for some window managers. (#704) diff --git a/README.md b/README.md index 2e4c56e9e6..495ebfd2f2 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libpcre2-dev libpixma On Fedora, the needed packages are ``` -dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel +dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel xcb-util-devel ``` To build the documents, you need `asciidoc` diff --git a/src/backend/backend.h b/src/backend/backend.h index 183b8bfc4c..154306d52f 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -209,7 +209,7 @@ struct backend_operations { * @param backend_data backend data * @param pixmap X pixmap to bind * @param fmt information of the pixmap's visual - * @param owned whether the ownership of the pixmap is transfered to the backend + * @param owned whether the ownership of the pixmap is transferred to the backend * @return backend internal data structure bound with this pixmap */ void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, @@ -225,7 +225,7 @@ struct backend_operations { struct backend_shadow_context *ctx); /// Create a shadow image based on the parameters. Resulting image should have a - /// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the + /// size of `width + radius * 2` x `height + radius * 2`. Radius is set when the /// shadow context is created. /// Default implementation: default_render_shadow /// @@ -284,9 +284,9 @@ struct backend_operations { bool (*is_image_transparent)(backend_t *backend_data, void *image_data) attr_nonnull(1, 2); - /// Get the age of the buffer content we are currently rendering ontop + /// Get the age of the buffer content we are currently rendering on top /// of. The buffer that has just been `present`ed has a buffer age of 1. - /// Everytime `present` is called, buffers get older. Return -1 if the + /// Every time `present` is called, buffers get older. Return -1 if the /// buffer is empty. /// /// Optional @@ -295,7 +295,7 @@ struct backend_operations { /// Get the render time of the last frame. If the render is still in progress, /// returns false. The time is returned in `ts`. Frames are delimited by the /// present() calls. i.e. after a present() call, last_render_time() should start - /// reporting the time of the just presen1ted frame. + /// reporting the time of the just presented frame. /// /// Optional, if not available, the most conservative estimation will be used. bool (*last_render_time)(backend_t *backend_data, struct timespec *ts); diff --git a/src/backend/driver.c b/src/backend/driver.c index f17743a4aa..99ea4d7a3a 100644 --- a/src/backend/driver.c +++ b/src/backend/driver.c @@ -19,11 +19,14 @@ void apply_driver_workarounds(struct session *ps, enum driver driver) { } } -enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver) { +enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver attr_unused) { + enum vblank_scheduler_type type = VBLANK_SCHEDULER_PRESENT; +#ifdef CONFIG_OPENGL if (driver & DRIVER_NVIDIA) { - return VBLANK_SCHEDULER_SGI_VIDEO_SYNC; + type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC; } - return VBLANK_SCHEDULER_PRESENT; +#endif + return type; } enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) { diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 138d0e1c51..44bca83f22 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -185,7 +185,7 @@ void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader) * @note In order to reduce number of textures which needs to be * allocated and deleted during this recursive render * we reuse the same two textures for render source and - * destination simply by alterating between them. + * destination simply by alternating between them. * Unfortunately on first iteration source_texture might * be read-only. In this case we will select auxiliary_texture as * destination_texture in order not to touch that read-only source @@ -253,7 +253,7 @@ _gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destina /* * @brief Builds a 1x1 texture which has color corresponding to the average of all - * pixels of img by recursively rendering into texture of quorter the size (half + * pixels of img by recursively rendering into texture of quarter the size (half * width and half height). * Returned texture must not be deleted, since it's owned by the gl_image. It will be * deleted when the gl_image is released. diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 2b05dbd0aa..bc8e09cc5f 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -78,7 +78,7 @@ typedef struct { GLint color_loc; } gl_fill_shader_t; -/// @brief Wrapper of a binded GL texture. +/// @brief Wrapper of a bound GL texture. struct gl_texture { int refcount; bool has_alpha; diff --git a/src/common.h b/src/common.h index 273e399188..21c0d0583a 100644 --- a/src/common.h +++ b/src/common.h @@ -244,8 +244,8 @@ typedef struct session { /// Either the backend is currently rendering a frame, or a frame has been /// rendered but has yet to be presented. In either case, we should not start /// another render right now. As if we start issuing rendering commands now, we - /// will have to wait for either the the current render to finish, or the current - /// back buffer to be become available again. In either case, we will be wasting + /// will have to wait for either the current render to finish, or the current + /// back buffer to become available again. In either case, we will be wasting /// time. bool backend_busy; /// Whether a render is queued. This generally means there are pending updates @@ -279,7 +279,7 @@ typedef struct session { struct x_convolution_kernel **blur_kerns_cache; /// If we should quit bool quit : 1; - // TODO(yshui) use separate flags for dfferent kinds of updates so we don't + // TODO(yshui) use separate flags for different kinds of updates so we don't // waste our time. /// Whether there are pending updates, like window creation, etc. bool pending_updates : 1; diff --git a/src/compiler.h b/src/compiler.h index 302bc7bbed..544daa559e 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -52,7 +52,7 @@ #else # define attr_warn_unused_result #endif -// An alias for conveninence +// An alias for convenience #define must_use attr_warn_unused_result #if __has_attribute(nonnull) diff --git a/src/config.c b/src/config.c index 3ab93f0a7e..13ec302ea7 100644 --- a/src/config.c +++ b/src/config.c @@ -563,7 +563,7 @@ static char *locate_auxiliary_file_at(const char *base, const char *scope, const } /** - * Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary + * Get a path of an auxiliary file to read, could be a shader file, or any supplementary * file. * * Follows the XDG specification to search for the shader file in configuration locations. diff --git a/src/config.h b/src/config.h index 0a736d3faf..d8e07fd853 100644 --- a/src/config.h +++ b/src/config.h @@ -366,7 +366,7 @@ char **xdg_config_dirs(void); /// Parse a configuration file /// Returns the actually config_file name used, allocated on heap /// Outputs: -/// shadow_enable = whether shaodw is enabled globally +/// shadow_enable = whether shadow is enabled globally /// fading_enable = whether fading is enabled globally /// win_option_mask = whether option overrides for specific window type is set for given /// options diff --git a/src/event.c b/src/event.c index 739540dc9a..28f67d8554 100644 --- a/src/event.c +++ b/src/event.c @@ -29,7 +29,7 @@ /// made the query when those events were already in the queue. so the reply you got is /// more up-to-date than the events). Also, handling events when other client are making /// concurrent requests is not good. Because the server states are changing without you -/// knowning them. This is super racy, and can cause lots of potential problems. +/// knowing them. This is super racy, and can cause lots of potential problems. /// /// All of above mandates we do these things: /// 1. Grab server when handling events @@ -324,7 +324,7 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t } if (ev->parent == ps->c.screen_info->root) { - // X will generate reparent notifiy even if the parent didn't actually + // X will generate reparent notify even if the parent didn't actually // change (i.e. reparent again to current parent). So we check if that's // the case auto w = find_win(ps, ev->window); @@ -465,7 +465,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } } - // Unconcerned about any other proprties on root window + // Unconcerned about any other properties on root window return; } @@ -499,7 +499,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) { - // Unnecessay until we remove the queue_redraw in ev_handle + // Unnecessary until we remove the queue_redraw in ev_handle queue_redraw(ps); } diff --git a/src/file_watch.c b/src/file_watch.c index faa8f68933..4532fb6173 100644 --- a/src/file_watch.c +++ b/src/file_watch.c @@ -158,7 +158,7 @@ bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void * fflags |= NOTE_CLOSE_WRITE; #else // NOTE_WRITE will receive notification more frequent than necessary, so is less - // preferrable + // preferable fflags |= NOTE_WRITE; #endif struct kevent ev = { diff --git a/src/kernel.c b/src/kernel.c index b5e1a487d5..a9865c9335 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -96,7 +96,7 @@ static inline double estimate_first_row_sum(double size, double r) { // `a` is gaussian at (size, 0) double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r; // The sum of the whole kernel is normalized to 1, i.e. each element is divided by - // factor sqaured. So the sum of the first row is a * factor / factor^2 = a / + // factor squared. So the sum of the first row is a * factor / factor^2 = a / // factor return a / factor; } diff --git a/src/opengl.c b/src/opengl.c index 4031e41bd5..7360640ce5 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -1129,7 +1129,7 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, // TODO(bhagwan) this is a mess and needs a more consistent way of getting the border // pixel I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in // xcb_create_window()) or a way to get the pixels from xcb_render_picture_t but the -// documentation for the xcb_xrender extension is literaly non existent... +// documentation for the xcb_xrender extension is literally non existent... // // NOTE(yshui) There is no consistent way to get the "border" color of a X window. From // the WM's perspective there are multiple ways to implement window borders. Using diff --git a/src/opengl.h b/src/opengl.h index affad2499a..cd074ff26e 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -75,7 +75,7 @@ typedef struct glx_session { glx_round_pass_t *round_passes; } glx_session_t; -/// @brief Wrapper of a binded GLX texture. +/// @brief Wrapper of a bound GLX texture. typedef struct _glx_texture { GLuint texture; GLXPixmap glpixmap; @@ -121,9 +121,9 @@ bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int wi void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2); /** - * Check if a texture is binded, or is binded to the given pixmap. + * Check if a texture is bound, or is bound to the given pixmap. */ -static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) { +static inline bool glx_tex_bound(const glx_texture_t *ptex, xcb_pixmap_t pixmap) { return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap); } diff --git a/src/options.c b/src/options.c index a1af3b5477..ca79b8d1f5 100644 --- a/src/options.c +++ b/src/options.c @@ -325,9 +325,9 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all int o = 0, longopt_idx = -1; - // Pre-parse the commandline arguments to check for --config and invalid + // Pre-parse the command line arguments to check for --config and invalid // switches - // Must reset optind to 0 here in case we reread the commandline + // Must reset optind to 0 here in case we reread the command line // arguments optind = 1; *config_file = NULL; @@ -379,7 +379,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // instead of commas in atof(). setlocale(LC_NUMERIC, "C"); - // Parse commandline arguments. Range checking will be done later. + // Parse command line arguments. Range checking will be done later. bool failed = false; const char *deprecation_message attr_unused = @@ -731,7 +731,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, opt->blur_strength = atoi(optarg); break; case 333: - // --cornor-radius + // --corner-radius opt->corner_radius = atoi(optarg); break; case 334: diff --git a/src/picom.c b/src/picom.c index 2de4e28710..c7eef127a8 100644 --- a/src/picom.c +++ b/src/picom.c @@ -270,7 +270,7 @@ enum vblank_callback_action reschedule_render_at_vblank(struct vblank_event *e, /// is no render currently scheduled. i.e. render_queued == false. /// 2. then, we need to figure out the best time to start rendering. we need to /// at least know when the next vblank will start, as we can't start render -/// before the current rendered frame is diplayed on screen. we have this +/// before the current rendered frame is displayed on screen. we have this /// information from the vblank scheduler, it will notify us when that happens. /// we might also want to delay the rendering even further to reduce latency, /// this is discussed below, in FUTURE WORKS. @@ -1330,14 +1330,43 @@ void root_damaged(session_t *ps) { } auto pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms); if (pixmap != XCB_NONE) { + xcb_get_geometry_reply_t *r = xcb_get_geometry_reply( + ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL); + if (!r) { + goto err; + } + + // We used to assume that pixmaps pointed by the root background + // pixmap atoms are owned by the root window and have the same + // depth and hence the same visual that we can use to bind them. + // However, some applications break this assumption, e.g. the + // Xfce's desktop manager xfdesktop that sets the _XROOTPMAP_ID + // atom to a pixmap owned by it that seems to always have 32 bpp + // depth when the common root window's depth is 24 bpp. So use the + // root window's visual only if the root background pixmap's depth + // matches the root window's depth. Otherwise, find a suitable + // visual for the root background pixmap's depth and use it. + // + // We can't obtain a suitable visual for the root background + // pixmap the same way as the win_bind_pixmap function because it + // requires a window and we have only a pixmap. We also can't not + // bind the root background pixmap in case of depth mismatch + // because some options rely on it's content, e.g. + // transparent-clipping. + xcb_visualid_t visual = + r->depth == ps->c.screen_info->root_depth + ? ps->c.screen_info->root_visual + : x_get_visual_for_depth(&ps->c, r->depth); + free(r); + ps->root_image = ps->backend_data->ops->bind_pixmap( - ps->backend_data, pixmap, - x_get_visual_info(&ps->c, ps->c.screen_info->root_visual), false); + ps->backend_data, pixmap, x_get_visual_info(&ps->c, visual), false); if (ps->root_image) { ps->backend_data->ops->set_image_property( ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE, ps->root_image, (int[]){ps->root_width, ps->root_height}); } else { + err: log_error("Failed to bind root back pixmap"); } } @@ -2040,7 +2069,7 @@ static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused /** * Turn on the program reset flag. * - * This will result in the compostior resetting itself after next paint. + * This will result in the compositor resetting itself after next paint. */ static void reset_enable(EV_P_ ev_signal *w attr_unused, int revents attr_unused) { log_info("picom is resetting..."); @@ -2117,11 +2146,11 @@ static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data) /** * Initialize a session. * - * @param argc number of commandline arguments - * @param argv commandline arguments + * @param argc number of command line arguments + * @param argv command line arguments * @param dpy the X Display * @param config_file the path to the config file - * @param all_xerros whether we should report all X errors + * @param all_xerrors whether we should report all X errors * @param fork whether we will fork after initialization */ static session_t *session_init(int argc, char **argv, Display *dpy, @@ -2673,7 +2702,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->server_grabbed = true; // We are going to pull latest information from X server now, events sent by X - // earlier is irrelavant at this point. + // earlier is irrelevant at this point. // A better solution is probably grabbing the server from the very start. But I // think there still could be race condition that mandates discarding the events. x_discard_events(&ps->c); diff --git a/src/render.c b/src/render.c index ec50d8a3ec..371a9be7d4 100644 --- a/src/render.c +++ b/src/render.c @@ -89,7 +89,7 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h fbcfg = ppaint->fbcfg; } - if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) { + if (force || !glx_tex_bound(ppaint->ptex, ppaint->pixmap)) { return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei, repeat, fbcfg); } @@ -378,7 +378,7 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) { } #ifdef CONFIG_OPENGL - if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, XCB_NONE)) { + if (BKEND_GLX == ps->o.backend && !glx_tex_bound(ppaint->ptex, XCB_NONE)) { return false; } #endif @@ -604,20 +604,27 @@ static bool get_root_tile(session_t *ps) { bool fill = false; xcb_pixmap_t pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms); - // Make sure the pixmap we got is valid - if (pixmap && !x_validate_pixmap(&ps->c, pixmap)) { - pixmap = XCB_NONE; + xcb_get_geometry_reply_t *r; + if (pixmap) { + r = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL); } // Create a pixmap if there isn't any - if (!pixmap) { + xcb_visualid_t visual; + if (!pixmap || !r) { pixmap = x_create_pixmap(&ps->c, (uint8_t)ps->c.screen_info->root_depth, 1, 1); if (pixmap == XCB_NONE) { log_error("Failed to create pixmaps for root tile."); return false; } + visual = ps->c.screen_info->root_visual; fill = true; + } else { + visual = r->depth == ps->c.screen_info->root_depth + ? ps->c.screen_info->root_visual + : x_get_visual_for_depth(&ps->c, r->depth); + free(r); } // Create Picture @@ -625,7 +632,7 @@ static bool get_root_tile(session_t *ps) { .repeat = true, }; ps->root_tile_paint.pict = x_create_picture_with_visual_and_pixmap( - &ps->c, ps->c.screen_info->root_visual, pixmap, XCB_RENDER_CP_REPEAT, &pa); + &ps->c, visual, pixmap, XCB_RENDER_CP_REPEAT, &pa); // Fill pixmap if needed if (fill) { @@ -646,8 +653,7 @@ static bool get_root_tile(session_t *ps) { ps->root_tile_paint.pixmap = pixmap; #ifdef CONFIG_OPENGL if (BKEND_GLX == ps->o.backend) { - return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, - ps->c.screen_info->root_visual, false); + return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, visual, false); } #endif diff --git a/src/statistics.c b/src/statistics.c index 11c7466ef6..1a3b335fc4 100644 --- a/src/statistics.c +++ b/src/statistics.c @@ -51,10 +51,6 @@ void render_statistics_add_render_time_sample(struct render_statistics *rs, int } /// How much time budget we should give to the backend for rendering, in microseconds. -/// -/// A `divisor` is also returned, indicating the target framerate. The divisor is -/// the number of vblanks we should wait between each frame. A divisor of 1 means -/// full framerate, 2 means half framerate, etc. unsigned int render_statistics_get_budget(struct render_statistics *rs) { if (rs->render_times.nelem < rs->render_times.window_size) { // No valid render time estimates yet. Assume maximum budget. diff --git a/src/utils.c b/src/utils.c index 5709fa21d2..53766d6d19 100644 --- a/src/utils.c +++ b/src/utils.c @@ -141,10 +141,10 @@ void rolling_max_pop_front(struct rolling_max *rm, int front) { } void rolling_max_push_back(struct rolling_max *rm, int val) { - // Update the prority queue. + // Update the priority queue. // Remove all elements smaller than the new element from the queue. Because // the new element will become the maximum element before them, and since they - // come b1efore the new element, they will have been popped before the new + // come before the new element, they will have been popped before the new // element, so they will never become the maximum element. while (rm->np) { int p_tail = IDX(rm->p_head + rm->np - 1); diff --git a/src/utils.h b/src/utils.h index 3960d751a0..f9c02602dd 100644 --- a/src/utils.h +++ b/src/utils.h @@ -236,7 +236,7 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr) ((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \ }) -/// @brief Wrapper of ealloc(). +/// @brief Wrapper of realloc(). #define crealloc(ptr, nmemb) \ ({ \ auto tmp = (nmemb); \ diff --git a/src/vblank.c b/src/vblank.c index ff83a9a76e..a9f82116f1 100644 --- a/src/vblank.c +++ b/src/vblank.c @@ -18,7 +18,6 @@ #include #include -#include "backend/gl/glx.h" #endif #include "compiler.h" @@ -63,9 +62,9 @@ struct present_vblank_scheduler { struct vblank_scheduler_ops { size_t size; - void (*init)(struct vblank_scheduler *self); + bool (*init)(struct vblank_scheduler *self); void (*deinit)(struct vblank_scheduler *self); - void (*schedule)(struct vblank_scheduler *self); + bool (*schedule)(struct vblank_scheduler *self); bool (*handle_x_events)(struct vblank_scheduler *self); }; @@ -78,13 +77,14 @@ struct sgi_video_sync_vblank_scheduler { // Since glXWaitVideoSyncSGI blocks, we need to run it in a separate thread. // ... and all the thread shenanigans that come with it. - _Atomic unsigned int last_msc; - _Atomic uint64_t last_ust; + _Atomic unsigned int current_msc; + _Atomic uint64_t current_ust; ev_async notify; pthread_t sync_thread; bool running, error; + unsigned int last_msc; - /// Protects `running`, `error` and `base.vblank_event_requested` + /// Protects `running`, and `base.vblank_event_requested` pthread_mutex_t vblank_requested_mtx; pthread_cond_t vblank_requested_cnd; }; @@ -96,6 +96,8 @@ struct sgi_video_sync_thread_args { pthread_cond_t start_cnd; }; +static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI; + static bool check_sgi_video_sync_extension(Display *dpy, int screen) { const char *glx_ext = glXQueryExtensionsString(dpy, screen); const char *needle = "GLX_SGI_video_sync"; @@ -207,8 +209,8 @@ static void *sgi_video_sync_thread(void *data) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - atomic_store(&self->last_msc, last_msc); - atomic_store(&self->last_ust, + atomic_store(&self->current_msc, last_msc); + atomic_store(&self->current_ust, (uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000)); ev_async_send(self->base.loop, &self->notify); pthread_mutex_lock(&self->vblank_requested_mtx); @@ -239,34 +241,30 @@ static void *sgi_video_sync_thread(void *data) { return NULL; } -static void sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) { +static bool sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) { auto self = (struct sgi_video_sync_vblank_scheduler *)base; - log_verbose("Requesting vblank event for msc %d", self->last_msc + 1); + if (self->error) { + return false; + } + log_verbose("Requesting vblank event for msc %d", self->current_msc + 1); pthread_mutex_lock(&self->vblank_requested_mtx); assert(!base->vblank_event_requested); base->vblank_event_requested = true; pthread_cond_signal(&self->vblank_requested_cnd); pthread_mutex_unlock(&self->vblank_requested_mtx); + return true; } static void -sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) { - auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify); - auto event = (struct vblank_event){ - .msc = atomic_load(&sched->last_msc), - .ust = atomic_load(&sched->last_ust), - }; - sched->base.vblank_event_requested = false; - log_verbose("Received vblank event for msc %lu", event.msc); - vblank_scheduler_invoke_callbacks(&sched->base, &event); -} +sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents); -static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) { +static bool sgi_video_sync_scheduler_init(struct vblank_scheduler *base) { auto self = (struct sgi_video_sync_vblank_scheduler *)base; auto args = (struct sgi_video_sync_thread_args){ .self = self, .start_status = -1, }; + bool succeeded = true; pthread_mutex_init(&args.start_mtx, NULL); pthread_cond_init(&args.start_cnd, NULL); @@ -286,11 +284,15 @@ static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) { if (args.start_status != 0) { log_fatal("Failed to start sgi_video_sync_thread, error code: %d", args.start_status); - abort(); + succeeded = false; + } else { + log_info("Started sgi_video_sync_thread"); } + self->error = !succeeded; + self->last_msc = 0; pthread_mutex_destroy(&args.start_mtx); pthread_cond_destroy(&args.start_cnd); - log_info("Started sgi_video_sync_thread"); + return succeeded; } static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) { @@ -306,15 +308,45 @@ static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) { pthread_mutex_destroy(&self->vblank_requested_mtx); pthread_cond_destroy(&self->vblank_requested_cnd); } + +static void +sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) { + auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify); + auto msc = atomic_load(&sched->current_msc); + if (sched->last_msc == msc) { + // NVIDIA spams us with duplicate vblank events after a suspend/resume + // cycle. Recreating the X connection and GLX context seems to fix this. + // Oh NVIDIA. + log_warn("Duplicate vblank event found with msc %d. Possible NVIDIA bug?", msc); + log_warn("Resetting the vblank scheduler"); + sgi_video_sync_scheduler_deinit(&sched->base); + sched->base.vblank_event_requested = false; + if (!sgi_video_sync_scheduler_init(&sched->base)) { + log_error("Failed to reset the vblank scheduler"); + } else { + sgi_video_sync_scheduler_schedule(&sched->base); + } + return; + } + auto event = (struct vblank_event){ + .msc = msc, + .ust = atomic_load(&sched->current_ust), + }; + sched->base.vblank_event_requested = false; + sched->last_msc = msc; + log_verbose("Received vblank event for msc %lu", event.msc); + vblank_scheduler_invoke_callbacks(&sched->base, &event); +} #endif -static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) { +static bool present_vblank_scheduler_schedule(struct vblank_scheduler *base) { auto self = (struct present_vblank_scheduler *)base; log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64, base->target_window, self->last_msc + 1); assert(!base->vblank_event_requested); x_request_vblank_event(base->c, base->target_window, self->last_msc + 1); base->vblank_event_requested = true; + return true; } static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unused revents) { @@ -327,7 +359,7 @@ static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unus vblank_scheduler_invoke_callbacks(&sched->base, &event); } -static void present_vblank_scheduler_init(struct vblank_scheduler *base) { +static bool present_vblank_scheduler_init(struct vblank_scheduler *base) { auto self = (struct present_vblank_scheduler *)base; base->type = VBLANK_SCHEDULER_PRESENT; ev_timer_init(&self->callback_timer, present_vblank_callback, 0, 0); @@ -339,6 +371,7 @@ static void present_vblank_scheduler_init(struct vblank_scheduler *base) { set_cant_fail_cookie(base->c, select_input); self->event = xcb_register_for_special_xge(base->c->c, &xcb_present_id, self->event_id, NULL); + return true; } static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) { @@ -439,17 +472,19 @@ static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDU #endif }; -static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) { +static bool vblank_scheduler_schedule_internal(struct vblank_scheduler *self) { assert(self->type < LAST_VBLANK_SCHEDULER); auto fn = vblank_scheduler_ops[self->type].schedule; assert(fn != NULL); - fn(self); + return fn(self); } bool vblank_scheduler_schedule(struct vblank_scheduler *self, vblank_callback_t vblank_callback, void *user_data) { if (self->callback_count == 0 && self->wind_down == 0) { - vblank_scheduler_schedule_internal(self); + if (!vblank_scheduler_schedule_internal(self)) { + return false; + } } if (self->callback_count == self->callback_capacity) { size_t new_capacity = diff --git a/src/win.c b/src/win.c index d5d77efa92..17e7628710 100644 --- a/src/win.c +++ b/src/win.c @@ -1222,7 +1222,7 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new // Delayed update of shadow image // By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to - // re-create or release the shaodw in based on whether w->shadow + // re-create or release the shadow in based on whether w->shadow // is set. win_set_flags(w, WIN_FLAGS_SHADOW_STALE); @@ -1407,16 +1407,16 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w) * Determine if a window should have rounded corners. */ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) { - if (ps->o.corner_radius == 0) { - w->corner_radius = 0; - return; - } - void *radius_override = NULL; if (c2_match(ps, w, ps->o.corner_radius_rules, &radius_override)) { log_debug("Matched corner rule! %d", w->corner_radius); } + if (ps->o.corner_radius == 0 && !radius_override) { + w->corner_radius = 0; + return; + } + // Don't round full screen windows & excluded windows, // unless we find a corner override in corner_radius_rules if (!radius_override && ((w && win_is_fullscreen(ps, w)) || @@ -2259,7 +2259,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { // Add border width because we are using a different origin. // X thinks the top left of the inner window is the origin - // (for the bounding shape, althought xcb_get_geometry thinks + // (for the bounding shape, although xcb_get_geometry thinks // the outer top left (outer means outside of the window // border) is the origin), // We think the top left of the border is the origin @@ -2602,7 +2602,7 @@ bool destroy_win_start(session_t *ps, struct win *w) { HASH_DEL(ps->windows, w); if (!w->managed || mw->state == WSTATE_UNMAPPED) { - // Window is already unmapped, or is an unmanged window, just + // Window is already unmapped, or is an unmanaged window, just // destroy it destroy_win_finish(ps, w); return true; @@ -3031,7 +3031,7 @@ static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid } /** - * Check if a window is fulscreen using EWMH + * Check if a window is full-screen using EWMH * * TODO(yshui) cache this property */ diff --git a/src/win.h b/src/win.h index 8fb1815433..3bad0094a1 100644 --- a/src/win.h +++ b/src/win.h @@ -278,13 +278,13 @@ struct managed_win { switch_t shadow_force; /// Opacity of the shadow. Affected by window opacity and frame opacity. double shadow_opacity; - /// X offset of shadow. Affected by commandline argument. + /// X offset of shadow. Affected by command line argument. int shadow_dx; - /// Y offset of shadow. Affected by commandline argument. + /// Y offset of shadow. Affected by command line argument. int shadow_dy; - /// Width of shadow. Affected by window size and commandline argument. + /// Width of shadow. Affected by window size and command line argument. int shadow_width; - /// Height of shadow. Affected by window size and commandline argument. + /// Height of shadow. Affected by window size and command line argument. int shadow_height; /// Picture to render shadow. Affected by window size. paint_t shadow_paint; diff --git a/src/win_defs.h b/src/win_defs.h index e032bc747c..10ad0238a4 100644 --- a/src/win_defs.h +++ b/src/win_defs.h @@ -68,7 +68,7 @@ typedef enum { } winstate_t; enum win_flags { - // Note: *_NONE flags are mostly redudant and meant for detecting logical errors + // Note: *_NONE flags are mostly redundant and meant for detecting logical errors // in the code /// pixmap is out of date, will be update in win_process_flags diff --git a/src/x.c b/src/x.c index 06c8b939d6..d48ae96ba7 100644 --- a/src/x.c +++ b/src/x.c @@ -321,6 +321,21 @@ xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standa return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id); } +xcb_visualid_t x_get_visual_for_depth(struct x_connection *c, uint8_t depth) { + xcb_screen_iterator_t screen_it = xcb_setup_roots_iterator(xcb_get_setup(c->c)); + for (; screen_it.rem; xcb_screen_next(&screen_it)) { + xcb_depth_iterator_t depth_it = + xcb_screen_allowed_depths_iterator(screen_it.data); + for (; depth_it.rem; xcb_depth_next(&depth_it)) { + if (depth_it.data->depth == depth) { + return xcb_depth_visuals_iterator(depth_it.data).data->visual_id; + } + } + } + + return XCB_NONE; +} + xcb_render_pictformat_t x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std) { x_get_server_pictfmts(c); @@ -689,27 +704,6 @@ xcb_pixmap_t x_create_pixmap(struct x_connection *c, uint8_t depth, int width, i return XCB_NONE; } -/** - * Validate a pixmap. - * - * Detect whether the pixmap is valid with XGetGeometry. Well, maybe there - * are better ways. - */ -bool x_validate_pixmap(struct x_connection *c, xcb_pixmap_t pixmap) { - if (pixmap == XCB_NONE) { - return false; - } - - auto r = xcb_get_geometry_reply(c->c, xcb_get_geometry(c->c, pixmap), NULL); - if (!r) { - return false; - } - - bool ret = r->width && r->height; - free(r); - return ret; -} - /// We don't use the _XSETROOT_ID root window property as a source of the background /// pixmap because it most likely points to a dummy pixmap used to keep the colormap /// associated with the background pixmap alive but we listen for it's changes and update diff --git a/src/x.h b/src/x.h index df45b5cca7..f320e8696e 100644 --- a/src/x.h +++ b/src/x.h @@ -356,8 +356,6 @@ const char *x_strerror(xcb_generic_error_t *e); xcb_pixmap_t x_create_pixmap(struct x_connection *, uint8_t depth, int width, int height); -bool x_validate_pixmap(struct x_connection *, xcb_pixmap_t pxmap); - /** * Free a winprop_t. * @@ -408,6 +406,8 @@ struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t vis xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standard_t std); +xcb_visualid_t x_get_visual_for_depth(struct x_connection *c, uint8_t depth); + xcb_render_pictformat_t x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std);