diff options
Diffstat (limited to 'compton.c')
-rw-r--r-- | compton.c | 1382 |
1 files changed, 1121 insertions, 261 deletions
@@ -35,37 +35,94 @@ const char *WINTYPES[NUM_WINTYPES] = { struct timeval time_start = { 0, 0 }; win *list; -Display *dpy; +Display *dpy = NULL; int scr; -Window root; -Picture root_picture; -Picture root_buffer; +/// Root window. +Window root = None; +/// Damage of root window. +Damage root_damage = None; +/// X Composite overlay window. Used if --paint-on-overlay. +Window overlay = None; + +/// Picture of root window. Destination of painting in no-DBE painting +/// mode. +Picture root_picture = None; +/// A Picture acting as the painting target. +Picture tgt_picture = None; +/// Temporary buffer to paint to before sending to display. +Picture tgt_buffer = None; +/// DBE back buffer for root window. Used in DBE painting mode. +XdbeBackBuffer root_dbe = None; + Picture black_picture; Picture cshadow_picture; /// Picture used for dimming inactive windows. Picture dim_picture = 0; Picture root_tile; XserverRegion all_damage; -#if HAS_NAME_WINDOW_PIXMAP Bool has_name_pixmap; -#endif int root_height, root_width; + +/// Pregenerated alpha pictures. +Picture *alpha_picts = NULL; /// Whether the program is idling. I.e. no fading, no potential window /// changes. Bool idling; +/// Whether all reg_ignore of windows should expire in this paint. +Bool reg_ignore_expire = False; +/// Window ID of the window we register as a symbol. +Window reg_win = 0; + +/// Currently used refresh rate. Used for sw_opti. +short refresh_rate = 0; +/// Interval between refresh in nanoseconds. Used for sw_opti. +unsigned long refresh_intv = 0; +/// Nanosecond-level offset of the first painting. Used for sw_opti. +long paint_tm_offset = 0; + +#ifdef CONFIG_VSYNC_DRM +/// File descriptor of DRI device file. Used for DRM VSync. +int drm_fd = 0; +#endif + +#ifdef CONFIG_VSYNC_OPENGL +/// GLX context. +GLXContext glx_context; + +/// Pointer to glXGetVideoSyncSGI function. Used by OpenGL VSync. +f_GetVideoSync glx_get_video_sync = NULL; + +/// Pointer to glXWaitVideoSyncSGI function. Used by OpenGL VSync. +f_WaitVideoSync glx_wait_video_sync = NULL; +#endif /* errors */ ignore *ignore_head = NULL, **ignore_tail = &ignore_head; int xfixes_event, xfixes_error; int damage_event, damage_error; int composite_event, composite_error; +int render_event, render_error; +int composite_opcode; + /// Whether X Shape extension exists. -Bool shape_exists = True; +Bool shape_exists = False; /// Event base number and error base number for X Shape extension. int shape_event, shape_error; -int render_event, render_error; -int composite_opcode; + +/// Whether X RandR extension exists. +Bool randr_exists = False; +/// Event base number and error base number for X RandR extension. +int randr_event, randr_error; + +#ifdef CONFIG_VSYNC_OPENGL +/// Whether X GLX extension exists. +Bool glx_exists = False; +/// Event base number and error base number for X GLX extension. +int glx_event, glx_error; +#endif + +Bool dbe_exists = False; /* shadows */ conv *gaussian_map; @@ -95,6 +152,7 @@ Atom client_atom; Atom name_atom; Atom name_ewmh_atom; Atom class_atom; +Atom transient_atom; Atom win_type_atom; Atom win_type[NUM_WINTYPES]; @@ -117,6 +175,12 @@ static options_t opts = { .fork_after_register = False, .synchronize = False, .detect_rounded_corners = False, + .paint_on_overlay = False, + + .refresh_rate = 0, + .sw_opti = False, + .vsync = VSYNC_NONE, + .dbe = False, .wintype_shadow = { False }, .shadow_red = 0.0, @@ -141,7 +205,9 @@ static options_t opts = { .inactive_opacity = 0, .inactive_opacity_override = False, .frame_opacity = 0.0, + .detect_client_opacity = False, .inactive_dim = 0.0, + .alpha_step = 0.03, .track_focus = False, .track_wdata = False, @@ -160,7 +226,7 @@ unsigned long fade_time = 0; * passed since the epoch. */ static unsigned long -get_time_in_milliseconds() { +get_time_ms() { struct timeval tv; gettimeofday(&tv, NULL); @@ -175,7 +241,7 @@ get_time_in_milliseconds() { */ static int fade_timeout(void) { - int diff = opts.fade_delta - get_time_in_milliseconds() + fade_time; + int diff = opts.fade_delta - get_time_ms() + fade_time; if (diff < 0) diff = 0; @@ -397,7 +463,7 @@ presum_gaussian(conv *map) { static XImage * make_shadow(Display *dpy, double opacity, - int width, int height) { + int width, int height, Bool clear_shadow) { XImage *ximage; unsigned char *data; int ylimit, xlimit; @@ -434,8 +500,8 @@ make_shadow(Display *dpy, double opacity, // later will be filled) could entirely cover the area of the shadow // that will be displayed, do not bother filling other pixels. If it // can't, we must fill the other pixels here. - if (!(opts.clear_shadow && opts.shadow_offset_x <= 0 && opts.shadow_offset_x >= -cgsize - && opts.shadow_offset_y <= 0 && opts.shadow_offset_y >= -cgsize)) { + /* if (!(clear_shadow && opts.shadow_offset_x <= 0 && opts.shadow_offset_x >= -cgsize + && opts.shadow_offset_y <= 0 && opts.shadow_offset_y >= -cgsize)) { */ if (cgsize > 0) { d = shadow_top[opacity_int * (cgsize + 1) + cgsize]; } else { @@ -443,7 +509,7 @@ make_shadow(Display *dpy, double opacity, opacity, center, center, width, height); } memset(data, d, sheight * swidth); - } + // } /* * corners @@ -506,7 +572,9 @@ make_shadow(Display *dpy, double opacity, } } - if (opts.clear_shadow) { + assert(!clear_shadow); + /* + if (clear_shadow) { // Clear the region in the shadow that the window would cover based // on shadow_offset_{x,y} user provides int xstart = normalize_i_range(- (int) opts.shadow_offset_x, 0, swidth); @@ -521,18 +589,20 @@ make_shadow(Display *dpy, double opacity, memset(&data[y * swidth + xstart], 0, xrange); } } + */ return ximage; } static Picture -shadow_picture(Display *dpy, double opacity, int width, int height) { +shadow_picture(Display *dpy, double opacity, int width, int height, + Bool clear_shadow) { XImage *shadow_image = NULL; Pixmap shadow_pixmap = None, shadow_pixmap_argb = None; Picture shadow_picture = None, shadow_picture_argb = None; GC gc = None; - shadow_image = make_shadow(dpy, opacity, width, height); + shadow_image = make_shadow(dpy, opacity, width, height, clear_shadow); if (!shadow_image) return None; @@ -718,6 +788,13 @@ win_match_once(win *w, const wincond *cond) { cond); #endif + if (InputOnly == w->a.class) { +#ifdef DEBUG_WINMATCH + printf(": InputOnly\n"); +#endif + return false; + } + // Determine the target target = NULL; switch (cond->target) { @@ -941,7 +1018,8 @@ determine_evmask(Display *dpy, Window wid, win_evmode_t mode) { } if (WIN_EVMODE_CLIENT == mode || find_toplevel(dpy, wid)) { - if (opts.frame_opacity || opts.track_wdata) + if (opts.frame_opacity || opts.track_wdata + || opts.detect_client_opacity) evmask |= PropertyChangeMask; } @@ -1047,6 +1125,11 @@ recheck_focus(Display *dpy) { static Picture root_tile_f(Display *dpy) { + /* + if (opts.paint_on_overlay) { + return root_picture; + } */ + Picture picture; Atom actual_type; Pixmap pixmap; @@ -1107,11 +1190,41 @@ paint_root(Display *dpy) { XRenderComposite( dpy, PictOpSrc, root_tile, None, - root_buffer, 0, 0, 0, 0, 0, 0, + tgt_buffer, 0, 0, 0, 0, 0, 0, root_width, root_height); } /** + * Get a rectangular region a window occupies, excluding shadow. + */ +static XserverRegion +win_get_region(Display *dpy, win *w) { + XRectangle r; + + r.x = w->a.x; + r.y = w->a.y; + r.width = w->widthb; + r.height = w->heightb; + + return XFixesCreateRegion(dpy, &r, 1); +} + +/** + * Get a rectangular region a window occupies, excluding frame and shadow. + */ +static XserverRegion +win_get_region_noframe(Display *dpy, win *w) { + XRectangle r; + + r.x = w->a.x + w->left_width; + r.y = w->a.y + w->top_width; + r.width = w->a.width; + r.height = w->a.height; + + return XFixesCreateRegion(dpy, &r, 1); +} + +/** * Get a rectangular region a window (and possibly its shadow) occupies. * * Note w->shadow and shadow geometry must be correct before calling this @@ -1236,23 +1349,38 @@ get_frame_extents(Display *dpy, win *w, Window client) { } } +static inline Picture +get_alpha_pict_d(double o) { + assert((lround(normalize_d(o) / opts.alpha_step)) <= lround(1.0 / opts.alpha_step)); + return alpha_picts[lround(normalize_d(o) / opts.alpha_step)]; +} + +static inline Picture +get_alpha_pict_o(opacity_t o) { + return get_alpha_pict_d((double) o / OPAQUE); +} + static win * paint_preprocess(Display *dpy, win *list) { win *w; win *t = NULL, *next = NULL; - // Sounds like the timeout in poll() frequently does not work - // accurately, asking it to wait to 20ms, and often it would wait for - // 19ms, so the step value has to be rounded. - unsigned steps = roundl((double) (get_time_in_milliseconds() - fade_time) / opts.fade_delta); - // Reset fade_time - fade_time = get_time_in_milliseconds(); + // Fading step calculation + unsigned steps = (sub_unslong(get_time_ms(), fade_time) + + FADE_DELTA_TOLERANCE * opts.fade_delta) / opts.fade_delta; + fade_time += steps * opts.fade_delta; + + XserverRegion last_reg_ignore = None; for (w = list; w; w = next) { // In case calling the fade callback function destroys this window next = w->next; opacity_t opacity_old = w->opacity; + // Destroy reg_ignore on all windows if they should expire + if (reg_ignore_expire) + free_region(dpy, &w->reg_ignore); + #if CAN_DO_USABLE if (!w->usable) continue; #endif @@ -1274,7 +1402,10 @@ paint_preprocess(Display *dpy, win *list) { add_damage_win(dpy, w); } - if (!w->opacity) { + w->alpha_pict = get_alpha_pict_o(w->opacity); + + // End the game if we are using the 0 opacity alpha_pict + if (w->alpha_pict == alpha_picts[0]) { check_fade_fin(dpy, w); continue; } @@ -1285,13 +1416,11 @@ paint_preprocess(Display *dpy, win *list) { XRenderPictFormat *format; Drawable draw = w->id; -#if HAS_NAME_WINDOW_PIXMAP if (has_name_pixmap && !w->pixmap) { set_ignore(dpy, NextRequest(dpy)); w->pixmap = XCompositeNameWindowPixmap(dpy, w->id); } if (w->pixmap) draw = w->pixmap; -#endif format = XRenderFindVisualFormat(dpy, w->a.visual); pa.subwindow_mode = IncludeInferiors; @@ -1312,31 +1441,23 @@ paint_preprocess(Display *dpy, win *list) { add_damage_win(dpy, w); } - // Rebuild alpha_pict only if necessary - if (OPAQUE != w->opacity - && (!w->alpha_pict || w->opacity != w->opacity_cur)) { - free_picture(dpy, &w->alpha_pict); - w->alpha_pict = solid_picture( - dpy, False, get_opacity_percent(dpy, w), 0, 0, 0); - w->opacity_cur = w->opacity; - } - // Calculate frame_opacity - if (opts.frame_opacity && 1.0 != opts.frame_opacity && w->top_width) - w->frame_opacity = get_opacity_percent(dpy, w) * opts.frame_opacity; - else - w->frame_opacity = 0.0; + { + double frame_opacity_old = w->frame_opacity; + + if (opts.frame_opacity && 1.0 != opts.frame_opacity + && w->top_width) + w->frame_opacity = get_opacity_percent(dpy, w) * + opts.frame_opacity; + else + w->frame_opacity = 0.0; - // Rebuild frame_alpha_pict only if necessary - if (w->frame_opacity - && (!w->frame_alpha_pict - || w->frame_opacity != w->frame_opacity_cur)) { - free_picture(dpy, &w->frame_alpha_pict); - w->frame_alpha_pict = solid_picture( - dpy, False, w->frame_opacity, 0, 0, 0); - w->frame_opacity_cur = w->frame_opacity; + if ((0.0 == frame_opacity_old) != (0.0 == w->frame_opacity)) + reg_ignore_expire = True; } + w->frame_alpha_pict = get_alpha_pict_d(w->frame_opacity); + // Calculate shadow opacity if (w->frame_opacity) w->shadow_opacity = opts.shadow_opacity * w->frame_opacity; @@ -1349,19 +1470,39 @@ paint_preprocess(Display *dpy, win *list) { if (w->shadow && !w->shadow_pict) { w->shadow_pict = shadow_picture(dpy, 1, - w->widthb, w->heightb); + w->widthb, w->heightb, False); } - // Rebuild shadow_alpha_pict if necessary - if (w->shadow - && (!w->shadow_alpha_pict - || w->shadow_opacity != w->shadow_opacity_cur)) { - free_picture(dpy, &w->shadow_alpha_pict); - w->shadow_alpha_pict = solid_picture( - dpy, False, w->shadow_opacity, 0, 0, 0); - w->shadow_opacity_cur = w->shadow_opacity; + w->shadow_alpha_pict = get_alpha_pict_d(w->shadow_opacity); + + // Generate ignore region for painting to reduce GPU load + if (reg_ignore_expire) { + free_region(dpy, &w->reg_ignore); + + // If the window is solid, we add the window region to the + // ignored region + if (WINDOW_SOLID == w->mode) { + if (!w->frame_opacity) + w->reg_ignore = win_get_region(dpy, w); + else + w->reg_ignore = win_get_region_noframe(dpy, w); + + XFixesIntersectRegion(dpy, w->reg_ignore, w->reg_ignore, + w->border_size); + + if (last_reg_ignore) + XFixesUnionRegion(dpy, w->reg_ignore, w->reg_ignore, + last_reg_ignore); + } + // Otherwise we copy the last region over + else if (last_reg_ignore) + w->reg_ignore = copy_region(dpy, last_reg_ignore); + else + w->reg_ignore = None; } + last_reg_ignore = w->reg_ignore; + // Reset flags w->flags = 0; @@ -1372,147 +1513,256 @@ paint_preprocess(Display *dpy, win *list) { return t; } +/** + * Paint the shadow of a window. + */ +static inline void +win_paint_shadow(Display *dpy, win *w, Picture tgt_buffer) { + XRenderComposite( + dpy, PictOpOver, w->shadow_pict, w->shadow_alpha_pict, + tgt_buffer, 0, 0, 0, 0, + w->a.x + w->shadow_dx, w->a.y + w->shadow_dy, + w->shadow_width, w->shadow_height); +} + +/** + * Paint a window itself and dim it if asked. + */ +static inline void +win_paint_win(Display *dpy, win *w, Picture tgt_buffer) { + int x = w->a.x; + int y = w->a.y; + int wid = w->widthb; + int hei = w->heightb; + + Picture alpha_mask = (OPAQUE == w->opacity ? None: w->alpha_pict); + int op = (w->mode == WINDOW_SOLID ? PictOpSrc: PictOpOver); + + if (!w->frame_opacity) { + XRenderComposite(dpy, op, w->picture, alpha_mask, + tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); + } + else { + unsigned int t = w->top_width; + unsigned int l = w->left_width; + unsigned int b = w->bottom_width; + unsigned int r = w->right_width; + + // top + XRenderComposite(dpy, PictOpOver, w->picture, w->frame_alpha_pict, + tgt_buffer, 0, 0, 0, 0, x, y, wid, t); + + // left + XRenderComposite(dpy, PictOpOver, w->picture, w->frame_alpha_pict, + tgt_buffer, 0, t, 0, t, x, y + t, l, hei - t); + + // bottom + XRenderComposite(dpy, PictOpOver, w->picture, w->frame_alpha_pict, + tgt_buffer, l, hei - b, l, hei - b, x + l, y + hei - b, wid - l - r, b); + + // right + XRenderComposite(dpy, PictOpOver, w->picture, w->frame_alpha_pict, + tgt_buffer, wid - r, t, wid - r, t, x + wid - r, y + t, r, hei - t); + + // body + XRenderComposite(dpy, op, w->picture, alpha_mask, tgt_buffer, + l, t, l, t, x + l, y + t, wid - l - r, hei - t - b); + + } + + // Dimming the window if needed + if (w->dim) { + XRenderComposite(dpy, PictOpOver, dim_picture, None, + tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); + } +} + static void paint_all(Display *dpy, XserverRegion region, win *t) { +#ifdef DEBUG_REPAINT + static struct timespec last_paint = { 0 }; +#endif + win *w; + XserverRegion reg_paint = None, reg_tmp = None, reg_tmp2 = None; if (!region) { region = get_screen_region(dpy); } + else { + // Remove the damaged area out of screen + XFixesIntersectRegion(dpy, region, region, get_screen_region(dpy)); + } #ifdef MONITOR_REPAINT - root_buffer = root_picture; + // Note: MONITOR_REPAINT cannot work with DBE right now. + tgt_buffer = tgt_picture; #else - if (!root_buffer) { - Pixmap root_pixmap = XCreatePixmap( - dpy, root, root_width, root_height, - DefaultDepth(dpy, scr)); + if (!tgt_buffer) { + // DBE painting mode: Directly paint to a Picture of the back buffer + if (opts.dbe) { + tgt_buffer = XRenderCreatePicture(dpy, root_dbe, + XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)), + 0, 0); + } + // No-DBE painting mode: Paint to an intermediate Picture then paint + // the Picture to root window + else { + Pixmap root_pixmap = XCreatePixmap( + dpy, root, root_width, root_height, + DefaultDepth(dpy, scr)); - root_buffer = XRenderCreatePicture(dpy, root_pixmap, - XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)), - 0, 0); + tgt_buffer = XRenderCreatePicture(dpy, root_pixmap, + XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)), + 0, 0); - XFreePixmap(dpy, root_pixmap); + XFreePixmap(dpy, root_pixmap); + } } #endif - XFixesSetPictureClipRegion(dpy, root_picture, 0, 0, region); + XFixesSetPictureClipRegion(dpy, tgt_picture, 0, 0, region); #ifdef MONITOR_REPAINT XRenderComposite( dpy, PictOpSrc, black_picture, None, - root_picture, 0, 0, 0, 0, 0, 0, + tgt_picture, 0, 0, 0, 0, 0, 0, root_width, root_height); #endif - paint_root(dpy); - #ifdef DEBUG_REPAINT printf("paint:"); #endif - for (w = t; w; w = w->prev_trans) { - int x, y, wid, hei; + if (t && t->reg_ignore) { + // Calculate the region upon which the root window is to be painted + // based on the ignore region of the lowest window, if available + reg_paint = reg_tmp = XFixesCreateRegion(dpy, NULL, 0); + XFixesSubtractRegion(dpy, reg_paint, region, t->reg_ignore); + } + else { + reg_paint = region; + } -#if HAS_NAME_WINDOW_PIXMAP - x = w->a.x; - y = w->a.y; - wid = w->widthb; - hei = w->heightb; -#else - x = w->a.x + w->a.border_width; - y = w->a.y + w->a.border_width; - wid = w->a.width; - hei = w->a.height; -#endif + XFixesSetPictureClipRegion(dpy, tgt_buffer, 0, 0, reg_paint); + paint_root(dpy); + + // Create temporary regions for use during painting + if (!reg_tmp) + reg_tmp = XFixesCreateRegion(dpy, NULL, 0); + reg_tmp2 = XFixesCreateRegion(dpy, NULL, 0); + + for (w = t; w; w = w->prev_trans) { #ifdef DEBUG_REPAINT printf(" %#010lx", w->id); #endif - // Allow shadow to be painted anywhere in the damaged region - XFixesSetPictureClipRegion(dpy, root_buffer, 0, 0, region); - // Painting shadow if (w->shadow) { - XRenderComposite( - dpy, PictOpOver, w->shadow_pict, w->shadow_alpha_pict, - root_buffer, 0, 0, 0, 0, - w->a.x + w->shadow_dx, w->a.y + w->shadow_dy, - w->shadow_width, w->shadow_height); - } + // Shadow is to be painted based on the ignore region of current + // window + if (w->reg_ignore) { + if (w == t) { + // If it's the first cycle and reg_tmp2 is not ready, calculate + // the paint region here + reg_paint = reg_tmp; + XFixesSubtractRegion(dpy, reg_paint, region, w->reg_ignore); + } + else { + // Otherwise, used the cached region during last cycle + reg_paint = reg_tmp2; + } + XFixesIntersectRegion(dpy, reg_paint, reg_paint, w->extents); + } + else { + reg_paint = reg_tmp; + XFixesIntersectRegion(dpy, reg_paint, region, w->extents); + } + // Clear the shadow here instead of in make_shadow() for saving GPU + // power and handling shaped windows + if (opts.clear_shadow) + XFixesSubtractRegion(dpy, reg_paint, reg_paint, w->border_size); - // The window only could be painted in its bounding region - XserverRegion paint_reg = XFixesCreateRegion(dpy, NULL, 0); - XFixesIntersectRegion(dpy, paint_reg, region, w->border_size); - XFixesSetPictureClipRegion(dpy, root_buffer, 0, 0, paint_reg); + // Detect if the region is empty before painting + if (region == reg_paint || !is_region_empty(dpy, reg_paint)) { + XFixesSetPictureClipRegion(dpy, tgt_buffer, 0, 0, reg_paint); - Picture alpha_mask = (OPAQUE == w->opacity ? None: w->alpha_pict); - int op = (w->mode == WINDOW_SOLID ? PictOpSrc: PictOpOver); + win_paint_shadow(dpy, w, tgt_buffer); + } + } - // Painting the window - if (!w->frame_opacity) { - XRenderComposite(dpy, op, w->picture, alpha_mask, - root_buffer, 0, 0, 0, 0, x, y, wid, hei); + // Calculate the region based on the reg_ignore of the next (higher) + // window and the bounding region + reg_paint = reg_tmp; + if (w->prev_trans && w->prev_trans->reg_ignore) { + XFixesSubtractRegion(dpy, reg_paint, region, + w->prev_trans->reg_ignore); + // Copy the subtracted region to be used for shadow painting in next + // cycle + XFixesCopyRegion(dpy, reg_tmp2, reg_paint); + XFixesIntersectRegion(dpy, reg_paint, reg_paint, w->border_size); } else { - unsigned int t = w->top_width; - unsigned int l = w->left_width; - unsigned int b = w->bottom_width; - unsigned int r = w->right_width; - - /* top */ - XRenderComposite( - dpy, PictOpOver, w->picture, w->frame_alpha_pict, root_buffer, - 0, 0, 0, 0, x, y, wid, t); - - /* left */ - XRenderComposite( - dpy, PictOpOver, w->picture, w->frame_alpha_pict, root_buffer, - 0, t, 0, t, x, y + t, l, hei - t); - - /* bottom */ - XRenderComposite( - dpy, PictOpOver, w->picture, w->frame_alpha_pict, root_buffer, - l, hei - b, l, hei - b, x + l, y + hei - b, wid - l - r, b); - - /* right */ - XRenderComposite( - dpy, PictOpOver, w->picture, w->frame_alpha_pict, root_buffer, - wid - r, t, wid - r, t, x + wid - r, y + t, r, hei - t); - - /* body */ - XRenderComposite( - dpy, op, w->picture, alpha_mask, root_buffer, - l, t, l, t, x + l, y + t, wid - l - r, hei - t - b); - + XFixesIntersectRegion(dpy, reg_paint, region, w->border_size); } - // Dimming the window if needed - if (w->dim) { - XRenderComposite(dpy, PictOpOver, dim_picture, None, - root_buffer, 0, 0, 0, 0, x, y, wid, hei); - } + if (!is_region_empty(dpy, reg_paint)) { + XFixesSetPictureClipRegion(dpy, tgt_buffer, 0, 0, reg_paint); - XFixesDestroyRegion(dpy, paint_reg); + // Painting the window + win_paint_win(dpy, w, tgt_buffer); + } check_fade_fin(dpy, w); } #ifdef DEBUG_REPAINT printf("\n"); - fflush(stdout); #endif + // Free up all temporary regions XFixesDestroyRegion(dpy, region); - - if (root_buffer != root_picture) { - XFixesSetPictureClipRegion(dpy, root_buffer, 0, 0, None); + XFixesDestroyRegion(dpy, reg_tmp); + XFixesDestroyRegion(dpy, reg_tmp2); + + // Do this as early as possible + if (!opts.dbe) + XFixesSetPictureClipRegion(dpy, tgt_buffer, 0, 0, None); + + // Wait for VBlank + vsync_wait(); + + // DBE painting mode, only need to swap the buffer + if (opts.dbe) { + XdbeSwapInfo swap_info = { + .swap_window = (opts.paint_on_overlay ? overlay: root), + // Is it safe to use XdbeUndefined? + .swap_action = XdbeCopied + }; + XdbeSwapBuffers(dpy, &swap_info, 1); + } + // No-DBE painting mode + else if (tgt_buffer != tgt_picture) { XRenderComposite( - dpy, PictOpSrc, root_buffer, None, - root_picture, 0, 0, 0, 0, + dpy, PictOpSrc, tgt_buffer, None, + tgt_picture, 0, 0, 0, 0, 0, 0, root_width, root_height); } + + XFlush(dpy); + +#ifdef DEBUG_REPAINT + // It prints the timestamp in the wrong line, but... + print_timestamp(); + struct timespec now = get_time_timespec(); + struct timespec diff = { 0 }; + timespec_subtract(&diff, &now, &last_paint); + printf("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec); + last_paint = now; + fflush(stdout); +#endif + } static void @@ -1542,6 +1792,10 @@ repair_win(Display *dpy, win *w) { w->a.y + w->a.border_width); } + // Remove the part in the damage area that could be ignored + if (!reg_ignore_expire && w->prev_trans && w->prev_trans->reg_ignore) + XFixesSubtractRegion(dpy, parts, parts, w->prev_trans->reg_ignore); + add_damage(dpy, parts); w->damaged = 1; } @@ -1578,37 +1832,16 @@ get_wintype_prop(Display *dpy, Window wid) { return WINTYPE_UNKNOWN; } -static wintype -determine_wintype(Display *dpy, Window w) { - Window *children = NULL; - unsigned int nchildren, i; - wintype type; - - type = get_wintype_prop(dpy, w); - if (type != WINTYPE_UNKNOWN) return type; - - if (!wid_get_children(dpy, w, &children, &nchildren)) - return WINTYPE_UNKNOWN; - - for (i = 0; i < nchildren; i++) { - type = determine_wintype(dpy, children[i]); - if (type != WINTYPE_UNKNOWN) return type; - } - - if (children) { - XFree((void *)children); - } - - return WINTYPE_UNKNOWN; -} - static void map_win(Display *dpy, Window id, unsigned long sequence, Bool fade, Bool override_redirect) { win *w = find_win(dpy, id); - if (!w) return; + // Don't care about window mapping if it's an InputOnly window + if (!w || InputOnly == w->a.class) return; + + reg_ignore_expire = True; w->focused = False; w->a.map_state = IsViewable; @@ -1625,10 +1858,21 @@ map_win(Display *dpy, Window id, // Detect client window here instead of in add_win() as the client // window should have been prepared at this point if (!w->client_win) { - Window cw = find_client_win(dpy, w->id); + Window cw = 0; + // Always recursively look for a window with WM_STATE, as Fluxbox + // sets override-redirect flags on all frame windows. + cw = find_client_win(dpy, w->id); #ifdef DEBUG_CLIENTWIN printf("find_client_win(%#010lx): client %#010lx\n", w->id, cw); #endif + // Set a window's client window to itself only if we didn't find a + // client window and the window has override-redirect flag + if (!cw && w->a.override_redirect) { + cw = w->id; +#ifdef DEBUG_CLIENTWIN + printf("find_client_win(%#010lx): client self (override-redirected)\n", w->id); +#endif + } if (cw) { mark_client_win(dpy, w, cw); } @@ -1639,8 +1883,10 @@ map_win(Display *dpy, Window id, get_frame_extents(dpy, w, w->client_win); } - if (WINTYPE_UNKNOWN == w->window_type) - w->window_type = determine_wintype(dpy, w->id); + // Workaround for _NET_WM_WINDOW_TYPE for Openbox menus, which is + // set on a non-override-redirect window with no WM_STATE either + if (!w->client_win && WINTYPE_UNKNOWN == w->window_type) + w->window_type = get_wintype_prop(dpy, w->id); #ifdef DEBUG_WINTYPE printf("map_win(%#010lx): type %s\n", @@ -1648,11 +1894,7 @@ map_win(Display *dpy, Window id, #endif // Detect if the window is shaped or has rounded corners - if (opts.shadow_ignore_shaped) { - w->bounding_shaped = wid_bounding_shaped(dpy, w->id); - if (w->bounding_shaped && opts.detect_rounded_corners) - win_rounded_corners(dpy, w); - } + win_update_shape(dpy, w); // Get window name and class if we are tracking them if (opts.track_wdata) { @@ -1734,9 +1976,7 @@ finish_unmap_win(Display *dpy, win *w) { w->extents = None; } -#if HAS_NAME_WINDOW_PIXMAP free_pixmap(dpy, &w->pixmap); -#endif free_picture(dpy, &w->picture); free_region(dpy, &w->border_size); @@ -1754,6 +1994,8 @@ unmap_win(Display *dpy, Window id, Bool fade) { if (!w) return; + reg_ignore_expire = True; + w->a.map_state = IsUnmapped; // Fading out @@ -1774,14 +2016,14 @@ unmap_win(Display *dpy, Window id, Bool fade) { } static opacity_t -get_opacity_prop(Display *dpy, win *w, opacity_t def) { +wid_get_opacity_prop(Display *dpy, Window wid, opacity_t def) { Atom actual; int format; unsigned long n, left; unsigned char *data; int result = XGetWindowProperty( - dpy, w->id, opacity_atom, 0L, 1L, False, + dpy, wid, opacity_atom, 0L, 1L, False, XA_CARDINAL, &actual, &format, &n, &left, &data); if (result == Success && data != NULL) { @@ -1820,6 +2062,11 @@ determine_mode(Display *dpy, win *w) { mode = WINDOW_SOLID; } + // Expire reg_ignore if the window mode changes from solid to not, or + // vice versa + if ((WINDOW_SOLID == mode) != (WINDOW_SOLID == w->mode)) + reg_ignore_expire = True; + w->mode = mode; } @@ -1855,13 +2102,18 @@ calc_opacity(Display *dpy, win *w, Bool refetch_prop) { // Do not refetch the opacity window attribute unless necessary, this // is probably an expensive operation in some cases if (refetch_prop) { - w->opacity_prop = get_opacity_prop(dpy, w, OPAQUE); + w->opacity_prop = wid_get_opacity_prop(dpy, w->id, OPAQUE); + if (!opts.detect_client_opacity || !w->client_win + || w->id == w->client_win) + w->opacity_prop_client = OPAQUE; + else + w->opacity_prop_client = wid_get_opacity_prop(dpy, w->client_win, + OPAQUE); } - if (OPAQUE == (opacity = w->opacity_prop)) { - if (1.0 != opts.wintype_opacity[w->window_type]) { - opacity = opts.wintype_opacity[w->window_type] * OPAQUE; - } + if (OPAQUE == (opacity = w->opacity_prop) + && OPAQUE == (opacity = w->opacity_prop_client)) { + opacity = opts.wintype_opacity[w->window_type] * OPAQUE; } // Respect inactive_opacity in some cases @@ -1898,6 +2150,30 @@ determine_fade(Display *dpy, win *w) { } /** + * Update window-shape related information. + */ +static void +win_update_shape(Display *dpy, win *w) { + if (shape_exists && (opts.shadow_ignore_shaped /* || opts.clear_shadow */)) { + // Bool bounding_shaped_old = w->bounding_shaped; + + w->bounding_shaped = wid_bounding_shaped(dpy, w->id); + if (w->bounding_shaped && opts.detect_rounded_corners) + win_rounded_corners(dpy, w); + + // Shadow state could be changed + determine_shadow(dpy, w); + + /* + // If clear_shadow state on the window possibly changed, destroy the old + // shadow_pict + if (opts.clear_shadow && w->bounding_shaped != bounding_shaped_old) + free_picture(dpy, &w->shadow_pict); + */ + } +} + +/** * Determine if a window should have shadow, and update things depending * on shadow state. */ @@ -1962,14 +2238,28 @@ static void mark_client_win(Display *dpy, win *w, Window client) { w->client_win = client; + XSelectInput(dpy, client, determine_evmask(dpy, client, WIN_EVMODE_CLIENT)); + // Get the frame width and monitor further frame width changes on client // window if necessary if (opts.frame_opacity) { get_frame_extents(dpy, w, client); } - XSelectInput(dpy, client, determine_evmask(dpy, client, WIN_EVMODE_CLIENT)); + + // Detect window type here if (WINTYPE_UNKNOWN == w->window_type) w->window_type = get_wintype_prop(dpy, w->client_win); + + // Conform to EWMH standard, if _NET_WM_WINDOW_TYPE is not present, take + // override-redirect windows or windows without WM_TRANSIENT_FOR as + // _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG. + if (WINTYPE_UNKNOWN == w->window_type) { + if (w->a.override_redirect + || !wid_has_attr(dpy, client, transient_atom)) + w->window_type = WINTYPE_NORMAL; + else + w->window_type = WINTYPE_DIALOG; + } } static void @@ -2004,9 +2294,7 @@ add_win(Display *dpy, Window id, Window prev, Bool override_redirect) { #if CAN_DO_USABLE new->usable = False; #endif -#if HAS_NAME_WINDOW_PIXMAP new->pixmap = None; -#endif new->picture = None; if (new->a.class == InputOnly) { @@ -2027,10 +2315,10 @@ add_win(Display *dpy, Window id, Window prev, Bool override_redirect) { new->rounded_corners = False; new->border_size = None; + new->reg_ignore = None; new->extents = None; new->shadow = False; new->shadow_opacity = 0.0; - new->shadow_opacity_cur = 0.0; new->shadow_pict = None; new->shadow_alpha_pict = None; new->shadow_dx = 0; @@ -2039,14 +2327,13 @@ add_win(Display *dpy, Window id, Window prev, Bool override_redirect) { new->shadow_height = 0; new->opacity = 0; new->opacity_tgt = 0; - new->opacity_cur = OPAQUE; new->opacity_prop = OPAQUE; + new->opacity_prop_client = OPAQUE; new->fade = False; new->fade_callback = NULL; new->fade_fin = False; new->alpha_pict = None; new->frame_opacity = 1.0; - new->frame_opacity_cur = 1.0; new->frame_alpha_pict = None; new->dim = False; new->focused = False; @@ -2054,7 +2341,7 @@ add_win(Display *dpy, Window id, Window prev, Bool override_redirect) { new->need_configure = False; new->window_type = WINTYPE_UNKNOWN; - new->prev_trans = 0; + new->prev_trans = NULL; new->left_width = 0; new->right_width = 0; @@ -2145,9 +2432,9 @@ configure_win(Display *dpy, XConfigureEvent *ce) { if (!w) { if (ce->window == root) { - if (root_buffer) { - XRenderFreePicture(dpy, root_buffer); - root_buffer = None; + if (tgt_buffer) { + XRenderFreePicture(dpy, tgt_buffer); + tgt_buffer = None; } root_width = ce->width; root_height = ce->height; @@ -2165,6 +2452,8 @@ configure_win(Display *dpy, XConfigureEvent *ce) { restack_win(dpy, w, ce->above); } + reg_ignore_expire = True; + w->need_configure = False; #if CAN_DO_USABLE @@ -2181,10 +2470,8 @@ configure_win(Display *dpy, XConfigureEvent *ce) { w->a.y = ce->y; if (w->a.width != ce->width || w->a.height != ce->height) { -#if HAS_NAME_WINDOW_PIXMAP free_pixmap(dpy, &w->pixmap); free_picture(dpy, &w->picture); -#endif } if (w->a.width != ce->width || w->a.height != ce->height @@ -2235,10 +2522,9 @@ finish_destroy_win(Display *dpy, Window id) { finish_unmap_win(dpy, w); *prev = w->next; - free_picture(dpy, &w->alpha_pict); - free_picture(dpy, &w->frame_alpha_pict); free_picture(dpy, &w->shadow_pict); free_damage(dpy, &w->damage); + free_region(dpy, &w->reg_ignore); free(w->name); free(w->class_instance); free(w->class_general); @@ -2249,12 +2535,10 @@ finish_destroy_win(Display *dpy, Window id) { } } -#if HAS_NAME_WINDOW_PIXMAP static void destroy_callback(Display *dpy, win *w) { finish_destroy_win(dpy, w->id); } -#endif static void destroy_win(Display *dpy, Window id, Bool fade) { @@ -2269,8 +2553,33 @@ destroy_win(Display *dpy, Window id, Bool fade) { } } +static inline void +root_damaged(void) { + if (root_tile) { + XClearArea(dpy, root, 0, 0, 0, 0, True); + // if (root_picture != root_tile) { + XRenderFreePicture(dpy, root_tile); + root_tile = None; + /* } + if (root_damage) { + XserverRegion parts = XFixesCreateRegion(dpy, 0, 0); + XDamageSubtract(dpy, root_damage, None, parts); + add_damage(dpy, parts); + } */ + } + // Mark screen damaged if we are painting on overlay + if (opts.paint_on_overlay) + add_damage(dpy, get_screen_region(dpy)); +} + static void damage_win(Display *dpy, XDamageNotifyEvent *de) { + /* + if (root == de->drawable) { + root_damaged(); + return; + } */ + win *w = find_win(dpy, de->drawable); if (!w) return; @@ -2769,24 +3078,29 @@ ev_expose(XExposeEvent *ev) { inline static void ev_property_notify(XPropertyEvent *ev) { - int p; - for (p = 0; background_props[p]; p++) { - if (ev->atom == XInternAtom(dpy, background_props[p], False)) { - if (root_tile) { - XClearArea(dpy, root, 0, 0, 0, 0, True); - XRenderFreePicture(dpy, root_tile); - root_tile = None; + // Destroy the root "image" if the wallpaper probably changed + if (root == ev->window) { + for (int p = 0; background_props[p]; p++) { + if (ev->atom == XInternAtom(dpy, background_props[p], False)) { + root_damaged(); break; } } + // Unconcerned about any other proprties on root window + return; } - /* check if Trans property was changed */ + // If _NET_WM_OPACITY changes if (ev->atom == opacity_atom) { - /* reset mode and redraw window */ - win *w = find_win(dpy, ev->window); + win *w = NULL; + if ((w = find_win(dpy, ev->window))) + w->opacity_prop = wid_get_opacity_prop(dpy, w->id, OPAQUE); + else if (opts.detect_client_opacity + && (w = find_toplevel(dpy, ev->window))) + w->opacity_prop_client = wid_get_opacity_prop(dpy, w->client_win, + OPAQUE); if (w) { - calc_opacity(dpy, w, True); + calc_opacity(dpy, w, False); } } @@ -2845,17 +3159,25 @@ ev_shape_notify(XShapeEvent *ev) { } // Redo bounding shape detection and rounded corner detection - if (opts.shadow_ignore_shaped) { - w->bounding_shaped = wid_bounding_shaped(dpy, w->id); - if (w->bounding_shaped && opts.detect_rounded_corners) - win_rounded_corners(dpy, w); + win_update_shape(dpy, w); +} - // Shadow state could be changed - determine_shadow(dpy, w); +/** + * Handle ScreenChangeNotify events from X RandR extension. + */ +static void +ev_screen_change_notify(XRRScreenChangeNotifyEvent *ev) { + if (!opts.refresh_rate) { + update_refresh_rate(dpy); + if (!refresh_rate) { + fprintf(stderr, "ev_screen_change_notify(): Refresh rate detection " + "failed, software VSync disabled."); + opts.vsync = VSYNC_NONE; + } } } -inline static void +static void ev_handle(XEvent *ev) { if ((ev->type & 0x7f) != KeymapNotify) { discard_ignore(dpy, ev->xany.serial); @@ -2863,24 +3185,31 @@ ev_handle(XEvent *ev) { #ifdef DEBUG_EVENTS if (ev->type != damage_event + XDamageNotify) { - Window w; + Window wid; char *window_name; Bool to_free = False; - w = ev_window(ev); + wid = ev_window(ev); window_name = "(Failed to get title)"; - if (w) { - if (root == w) { + if (wid) { + if (root == wid) window_name = "(Root window)"; - } else { - to_free = (Bool) wid_get_name(dpy, w, &window_name); + else { + win *w = find_win(dpy, wid); + if (!w) + w = find_toplevel(dpy, wid); + + if (w && w->name) + window_name = w->name; + else + to_free = (Bool) wid_get_name(dpy, wid, &window_name); } } print_timestamp(); printf("event %10.10s serial %#010x window %#010lx \"%s\"\n", - ev_name(ev), ev_serial(ev), w, window_name); + ev_name(ev), ev_serial(ev), wid, window_name); if (to_free) { XFree(window_name); @@ -2929,6 +3258,10 @@ ev_handle(XEvent *ev) { ev_shape_notify((XShapeEvent *) ev); break; } + if (randr_exists && ev->type == (randr_event + RRScreenChangeNotify)) { + ev_screen_change_notify((XRRScreenChangeNotifyEvent *) ev); + break; + } if (ev->type == damage_event + XDamageNotify) { ev_damage_notify((XDamageNotifyEvent *)ev); } @@ -2940,11 +3273,14 @@ ev_handle(XEvent *ev) { * Main */ +/** + * Print usage text and exit. + */ static void usage(void) { - fprintf(stderr, "compton (development version)\n"); - fprintf(stderr, "usage: compton [options]\n"); - fprintf(stderr, + fputs( + "compton (development version)\n" + "usage: compton [options]\n" "Options:\n" "\n" "-d display\n" @@ -2983,7 +3319,7 @@ usage(void) { "-G\n" " Don't draw shadows on DND windows\n" "-b\n" - " Daemonize/background process.\n" + " Daemonize process.\n" "-S\n" " Enable synchronous operation (for debugging).\n" "--config path\n" @@ -3011,6 +3347,33 @@ usage(void) { "--detect-rounded-corners\n" " Try to detect windows with rounded corners and don't consider\n" " them shaped windows.\n" + "--detect-client-opacity\n" + " Detect _NET_WM_OPACITY on client windows, useful for window\n" + " managers not passing _NET_WM_OPACITY of client windows to frame\n" + " windows.\n" + "\n" + "--refresh-rate val\n" + " Specify refresh rate of the screen. If not specified or 0, compton\n" + " will try detecting this with X RandR extension.\n" + "--vsync vsync-method\n" + " Set VSync method. There are 2 VSync methods currently available:\n" + " none = No VSync\n" + " drm = VSync with DRM_IOCTL_WAIT_VBLANK. May only work on some\n" + " drivers. Experimental.\n" + " opengl = Try to VSync with SGI_swap_control OpenGL extension. Only\n" + " work on some drivers. Experimental.\n" + " (Note some VSync methods may not be enabled at compile time.)\n" + "--alpha-step val\n" + " Step for pregenerating alpha pictures. 0.01 - 1.0. Defaults to\n" + " 0.03.\n" + "--dbe\n" + " Enable DBE painting mode, intended to use with VSync to\n" + " (hopefully) eliminate tearing.\n" + "--paint-on-overlay\n" + " Painting on X Composite overlay window.\n" + "--sw-opti\n" + " Limit compton to repaint at most once every 1 / refresh_rate\n" + " second to boost performance. Experimental.\n" "\n" "Format of a condition:\n" "\n" @@ -3027,26 +3390,77 @@ usage(void) { " flag is \"i\" (ignore case).\n" "\n" " <pattern> is the actual pattern string.\n" - ); + , stderr); exit(1); } +/** + * Register a window as symbol, and initialize GLX context if wanted. + */ static void -register_cm(int scr) { - Window w; +register_cm(Bool want_glxct) { Atom a; char *buf; int len, s; - if (scr < 0) return; +#ifdef CONFIG_VSYNC_OPENGL + // Create a window with the wanted GLX visual + if (want_glxct) { + XVisualInfo *pvi = NULL; + Bool ret = False; + // Get visual for the window + int attribs[] = { GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, None }; + pvi = glXChooseVisual(dpy, scr, attribs); + + if (!pvi) { + fprintf(stderr, "register_cm(): Failed to choose visual required " + "by fake OpenGL VSync window. OpenGL VSync turned off.\n"); + } + else { + // Create the window + XSetWindowAttributes swa = { + .colormap = XCreateColormap(dpy, root, pvi->visual, AllocNone), + .border_pixel = 0, + }; + + pvi->screen = scr; + reg_win = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, pvi->depth, + InputOutput, pvi->visual, CWBorderPixel | CWColormap, &swa); + + if (!reg_win) + fprintf(stderr, "register_cm(): Failed to create window required " + "by fake OpenGL VSync. OpenGL VSync turned off.\n"); + else { + // Get GLX context + glx_context = glXCreateContext(dpy, pvi, None, GL_TRUE); + if (!glx_context) { + fprintf(stderr, "register_cm(): Failed to get GLX context. " + "OpenGL VSync turned off.\n"); + opts.vsync = VSYNC_NONE; + } + else { + // Attach GLX context + if (!(ret = glXMakeCurrent(dpy, reg_win, glx_context))) + fprintf(stderr, "register_cm(): Failed to attach GLX context." + " OpenGL VSync turned off.\n"); + } + } + } + if (pvi) + XFree(pvi); - w = XCreateSimpleWindow( - dpy, RootWindow(dpy, 0), - 0, 0, 1, 1, 0, None, None); + if (!ret) + opts.vsync = VSYNC_NONE; + } +#endif + + if (!reg_win) + reg_win = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, + None, None); Xutf8SetWMProperties( - dpy, w, "xcompmgr", "xcompmgr", + dpy, reg_win, "xcompmgr", "xcompmgr", NULL, 0, NULL, NULL, NULL); len = strlen(REGISTER_PROP) + 2; @@ -3063,7 +3477,7 @@ register_cm(int scr) { a = XInternAtom(dpy, buf, False); free(buf); - XSetSelectionOwner(dpy, a, w, 0); + XSetSelectionOwner(dpy, a, reg_win, 0); } static void @@ -3173,6 +3587,29 @@ open_config_file(char *cpath, char **ppath) { } /** + * Parse a VSync option argument. + */ +static inline void +parse_vsync(const char *optarg) { + const static char * const vsync_str[] = { + "none", // VSYNC_NONE + "drm", // VSYNC_DRM + "opengl", // VSYNC_OPENGL + }; + + vsync_t i; + + for (i = 0; i < (sizeof(vsync_str) / sizeof(vsync_str[0])); ++i) + if (!strcasecmp(optarg, vsync_str[i])) { + opts.vsync = i; + break; + } + if ((sizeof(vsync_str) / sizeof(vsync_str[0])) == i) { + fputs("Invalid --vsync argument. Ignored.\n", stderr); + } +} + +/** * Parse a configuration file from default location. */ static void @@ -3182,6 +3619,7 @@ parse_config(char *cpath, struct options_tmp *pcfgtmp) { config_t cfg; int ival = 0; double dval = 0.0; + const char *sval = NULL; f = open_config_file(cpath, &path); if (!f) { @@ -3271,6 +3709,22 @@ parse_config(char *cpath, struct options_tmp *pcfgtmp) { // --detect-rounded-corners lcfg_lookup_bool(&cfg, "detect-rounded-corners", &opts.detect_rounded_corners); + // --detect-client-opacity + lcfg_lookup_bool(&cfg, "detect-client-opacity", + &opts.detect_client_opacity); + // --refresh-rate + lcfg_lookup_int(&cfg, "refresh-rate", &opts.refresh_rate); + // --vsync + if (config_lookup_string(&cfg, "vsync", &sval)) + parse_vsync(sval); + // --alpha-step + config_lookup_float(&cfg, "alpha-step", &opts.alpha_step); + // --dbe + lcfg_lookup_bool(&cfg, "dbe", &opts.dbe); + // --paint-on-overlay + lcfg_lookup_bool(&cfg, "paint-on-overlay", &opts.paint_on_overlay); + // --sw-opti + lcfg_lookup_bool(&cfg, "sw-opti", &opts.sw_opti); // --shadow-exclude { config_setting_t *setting = @@ -3333,6 +3787,13 @@ get_cfg(int argc, char *const *argv) { { "no-fading-openclose", no_argument, NULL, 265 }, { "shadow-ignore-shaped", no_argument, NULL, 266 }, { "detect-rounded-corners", no_argument, NULL, 267 }, + { "detect-client-opacity", no_argument, NULL, 268 }, + { "refresh-rate", required_argument, NULL, 269 }, + { "vsync", required_argument, NULL, 270 }, + { "alpha-step", required_argument, NULL, 271 }, + { "dbe", no_argument, NULL, 272 }, + { "paint-on-overlay", no_argument, NULL, 273 }, + { "sw-opti", no_argument, NULL, 274 }, // Must terminate with a NULL entry { NULL, 0, NULL, 0 }, }; @@ -3487,6 +3948,34 @@ get_cfg(int argc, char *const *argv) { // --detect-rounded-corners opts.detect_rounded_corners = True; break; + case 268: + // --detect-client-opacity + opts.detect_client_opacity = True; + break; + case 269: + // --refresh-rate + opts.refresh_rate = atoi(optarg); + break; + case 270: + // --vsync + parse_vsync(optarg); + break; + case 271: + // --alpha-step + opts.alpha_step = atof(optarg); + break; + case 272: + // --dbe + opts.dbe = True; + break; + case 273: + // --paint-on-overlay + opts.paint_on_overlay = True; + break; + case 274: + // --sw-opti + opts.sw_opti = True; + break; default: usage(); break; @@ -3507,6 +3996,8 @@ get_cfg(int argc, char *const *argv) { opts.frame_opacity = normalize_d(opts.frame_opacity); opts.shadow_opacity = normalize_d(opts.shadow_opacity); cfgtmp.menu_opacity = normalize_d(cfgtmp.menu_opacity); + opts.refresh_rate = normalize_i_range(opts.refresh_rate, 0, 300); + opts.alpha_step = normalize_d_range(opts.alpha_step, 0.01, 1.0); if (OPAQUE == opts.inactive_opacity) { opts.inactive_opacity = 0; } @@ -3541,10 +4032,11 @@ get_atoms(void) { extents_atom = XInternAtom(dpy, "_NET_FRAME_EXTENTS", False); opacity_atom = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY", False); frame_extents_atom = XInternAtom(dpy, "_NET_FRAME_EXTENTS", False); - client_atom = XA_WM_CLASS; + client_atom = XInternAtom(dpy, "WM_STATE", False); name_atom = XA_WM_NAME; name_ewmh_atom = XInternAtom(dpy, "_NET_WM_NAME", False); class_atom = XA_WM_CLASS; + transient_atom = XA_WM_TRANSIENT_FOR; win_type_atom = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); @@ -3579,6 +4071,305 @@ get_atoms(void) { "_NET_WM_WINDOW_TYPE_DND", False); } +/** + * Update refresh rate info with X Randr extension. + */ +static void +update_refresh_rate(Display *dpy) { + XRRScreenConfiguration* randr_info; + + if (!(randr_info = XRRGetScreenInfo(dpy, root))) + return; + refresh_rate = XRRConfigCurrentRate(randr_info); + + XRRFreeScreenConfigInfo(randr_info); + + if (refresh_rate) + refresh_intv = NS_PER_SEC / refresh_rate; + else + refresh_intv = 0; +} + +/** + * Initialize refresh-rated based software optimization. + * + * @return True for success, False otherwise + */ +static Bool +sw_opti_init(void) { + // Prepare refresh rate + // Check if user provides one + refresh_rate = opts.refresh_rate; + if (refresh_rate) + refresh_intv = NS_PER_SEC / refresh_rate; + + // Auto-detect refresh rate otherwise + if (!refresh_rate && randr_exists) { + update_refresh_rate(dpy); + } + + // Turn off vsync_sw if we can't get the refresh rate + if (!refresh_rate) + return False; + + // Monitor screen changes only if vsync_sw is enabled and we are using + // an auto-detected refresh rate + if (randr_exists && !opts.refresh_rate) + XRRSelectInput(dpy, root, RRScreenChangeNotify); + + return True; +} + +/** + * Get the smaller number that is bigger than <code>dividend</code> and is + * N times of <code>divisor</code>. + */ +static inline long +lceil_ntimes(long dividend, long divisor) { + // It's possible to use the more beautiful expression here: + // ret = ((dividend - 1) / divisor + 1) * divisor; + // But it does not work well for negative values. + long ret = dividend / divisor * divisor; + if (ret < dividend) + ret += divisor; + + return ret; +} + +/** + * Wait for events until next paint. + * + * Optionally use refresh-rate based optimization to reduce painting. + * + * @param fd struct pollfd used for poll() + * @param timeout second timeout (fading timeout) + * @return > 0 if we get some events, 0 if timeout is reached, < 0 on + * problems + */ +static int +evpoll(struct pollfd *fd, int timeout) { + // Always wait infinitely if asked so, to minimize CPU usage + if (timeout < 0) { + int ret = poll(fd, 1, timeout); + // Reset fade_time so the fading steps during idling are not counted + fade_time = get_time_ms(); + return ret; + } + + // Just do a poll() if we are not using optimization + if (!opts.sw_opti) + return poll(fd, 1, timeout); + + // Convert the old timeout to struct timespec + struct timespec next_paint_tmout = { + .tv_sec = timeout / MS_PER_SEC, + .tv_nsec = timeout % MS_PER_SEC * (NS_PER_SEC / MS_PER_SEC) + }; + + // Get the nanosecond offset of the time when the we reach the timeout + // I don't think a 32-bit long could overflow here. + long target_relative_offset = (next_paint_tmout.tv_nsec + get_time_timespec().tv_nsec - paint_tm_offset) % NS_PER_SEC; + if (target_relative_offset < 0) + target_relative_offset += NS_PER_SEC; + + assert(target_relative_offset >= 0); + + // If the target time is sufficiently close to a refresh time, don't add + // an offset, to avoid certain blocking conditions. + if ((target_relative_offset % NS_PER_SEC) < SW_OPTI_TOLERANCE) + return poll(fd, 1, timeout); + + // Add an offset so we wait until the next refresh after timeout + next_paint_tmout.tv_nsec += lceil_ntimes(target_relative_offset, refresh_intv) - target_relative_offset; + if (next_paint_tmout.tv_nsec > NS_PER_SEC) { + next_paint_tmout.tv_nsec -= NS_PER_SEC; + ++next_paint_tmout.tv_sec; + } + + return ppoll(fd, 1, &next_paint_tmout, NULL); +} + +/** + * Initialize DRM VSync. + * + * @return True for success, False otherwise + */ +static Bool +vsync_drm_init(void) { +#ifdef CONFIG_VSYNC_DRM + // Should we always open card0? + if ((drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) { + fprintf(stderr, "vsync_drm_init(): Failed to open device.\n"); + return False; + } + + if (vsync_drm_wait()) + return False; + + return True; +#else + fprintf(stderr, "Program not compiled with DRM VSync support.\n"); + return False; +#endif +} + +#ifdef CONFIG_VSYNC_DRM +/** + * Wait for next VSync, DRM method. + * + * Stolen from: https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp + */ +static int +vsync_drm_wait(void) { + int ret = -1; + drm_wait_vblank_t vbl; + + vbl.request.type = _DRM_VBLANK_RELATIVE, + vbl.request.sequence = 1; + + do { + ret = ioctl(drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl); + vbl.request.type &= ~_DRM_VBLANK_RELATIVE; + } while (ret && errno == EINTR); + + if (ret) + fprintf(stderr, "vsync_drm_wait(): VBlank ioctl did not work, " + "unimplemented in this drmver?\n"); + + return ret; + +} +#endif + +/** + * Initialize OpenGL VSync. + * + * Stolen from: http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e + * Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html + * + * @return True for success, False otherwise + */ +static Bool +vsync_opengl_init(void) { +#ifdef CONFIG_VSYNC_OPENGL + // Get video sync functions + glx_get_video_sync = (f_GetVideoSync) + glXGetProcAddress ((const GLubyte *) "glXGetVideoSyncSGI"); + glx_wait_video_sync = (f_WaitVideoSync) + glXGetProcAddress ((const GLubyte *) "glXWaitVideoSyncSGI"); + if (!glx_wait_video_sync || !glx_get_video_sync) { + fprintf(stderr, "vsync_opengl_init(): " + "Failed to get glXWait/GetVideoSyncSGI function.\n"); + return False; + } + + return True; +#else + fprintf(stderr, "Program not compiled with OpenGL VSync support.\n"); + return False; +#endif +} + +#ifdef CONFIG_VSYNC_OPENGL +/** + * Wait for next VSync, OpenGL method. + */ +static void +vsync_opengl_wait(void) { + unsigned vblank_count; + + glx_get_video_sync(&vblank_count); + glx_wait_video_sync(2, (vblank_count + 1) % 2, &vblank_count); + // I see some code calling glXSwapIntervalSGI(1) afterwards, is it required? +} +#endif + +/** + * Wait for next VSync. + */ +static void +vsync_wait(void) { + if (VSYNC_NONE == opts.vsync) + return; + +#ifdef CONFIG_VSYNC_DRM + if (VSYNC_DRM == opts.vsync) { + vsync_drm_wait(); + return; + } +#endif + +#ifdef CONFIG_VSYNC_OPENGL + if (VSYNC_OPENGL == opts.vsync) { + vsync_opengl_wait(); + return; + } +#endif + + // This place should not reached! + assert(0); + + return; +} + +/** + * Pregenerate alpha pictures. + */ +static void +init_alpha_picts(Display *dpy) { + int i; + int num = lround(1.0 / opts.alpha_step) + 1; + + alpha_picts = malloc(sizeof(Picture) * num); + + for (i = 0; i < num; ++i) { + double o = i * opts.alpha_step; + if ((1.0 - o) > opts.alpha_step) + alpha_picts[i] = solid_picture(dpy, False, o, 0, 0, 0); + else + alpha_picts[i] = None; + } +} + +/** + * Initialize double buffer. + */ +static void +init_dbe(void) { + if (!(root_dbe = XdbeAllocateBackBufferName(dpy, + (opts.paint_on_overlay ? overlay: root), XdbeCopied))) { + fprintf(stderr, "Failed to create double buffer. Double buffering " + "turned off.\n"); + opts.dbe = False; + return; + } +} + +/** + * Initialize X composite overlay window. + */ +static void +init_overlay(void) { + overlay = XCompositeGetOverlayWindow(dpy, root); + if (overlay) { + // Set window region of the overlay window, code stolen from + // compiz-0.8.8 + XserverRegion region = XFixesCreateRegion (dpy, NULL, 0); + XFixesSetWindowShapeRegion(dpy, overlay, ShapeBounding, 0, 0, 0); + XFixesSetWindowShapeRegion(dpy, overlay, ShapeInput, 0, 0, region); + XFixesDestroyRegion (dpy, region); + + // Retrieve DamageNotify on root window if we are painting on an + // overlay + // root_damage = XDamageCreate(dpy, root, XDamageReportNonEmpty); + } + else { + fprintf(stderr, "Cannot get X Composite overlay window. Falling " + "back to painting on root window.\n"); + opts.paint_on_overlay = False; + } +} + int main(int argc, char **argv) { XEvent ev; @@ -3599,7 +4390,7 @@ main(int argc, char **argv) { get_cfg(argc, argv); - fade_time = get_time_in_milliseconds(); + fade_time = get_time_ms(); dpy = XOpenDisplay(opts.display); if (!dpy) { @@ -3628,11 +4419,9 @@ main(int argc, char **argv) { XCompositeQueryVersion(dpy, &composite_major, &composite_minor); -#if HAS_NAME_WINDOW_PIXMAP if (composite_major > 0 || composite_minor >= 2) { has_name_pixmap = True; } -#endif if (!XDamageQueryExtension(dpy, &damage_event, &damage_error)) { fprintf(stderr, "No damage extension\n"); @@ -3644,15 +4433,70 @@ main(int argc, char **argv) { exit(1); } - if (!XShapeQueryExtension(dpy, &shape_event, &shape_error)) { - shape_exists = False; + // Query X Shape + if (XShapeQueryExtension(dpy, &shape_event, &shape_error)) { + shape_exists = True; } - register_cm(scr); + // Query X RandR + if (opts.sw_opti && !opts.refresh_rate) { + if (XRRQueryExtension(dpy, &randr_event, &randr_error)) + randr_exists = True; + else + fprintf(stderr, "No XRandR extension, automatic refresh rate " + "detection impossible.\n"); + } + +#ifdef CONFIG_VSYNC_OPENGL + // Query X GLX extension + if (VSYNC_OPENGL == opts.vsync) { + if (glXQueryExtension(dpy, &glx_event, &glx_error)) + glx_exists = True; + else { + fprintf(stderr, "No GLX extension, OpenGL VSync impossible.\n"); + opts.vsync = VSYNC_NONE; + } + } +#endif + + // Query X DBE extension + if (opts.dbe) { + int dbe_ver_major = 0, dbe_ver_minor = 0; + if (XdbeQueryExtension(dpy, &dbe_ver_major, &dbe_ver_minor)) + if (dbe_ver_major >= 1) + dbe_exists = True; + else + fprintf(stderr, "DBE extension version too low. Double buffering " + "impossible.\n"); + else { + fprintf(stderr, "No DBE extension. Double buffering impossible.\n"); + } + if (!dbe_exists) + opts.dbe = False; + } + + register_cm((VSYNC_OPENGL == opts.vsync)); + + // Initialize software optimization + if (opts.sw_opti) + opts.sw_opti = sw_opti_init(); + + // Initialize DRM/OpenGL VSync + if ((VSYNC_DRM == opts.vsync && !vsync_drm_init()) + || (VSYNC_OPENGL == opts.vsync && !vsync_opengl_init())) + opts.vsync = VSYNC_NONE; + + // Overlay must be initialized before double buffer + if (opts.paint_on_overlay) + init_overlay(); + + if (opts.dbe) + init_dbe(); if (opts.fork_after_register) fork_after(); get_atoms(); + init_alpha_picts(dpy); pa.subwindow_mode = IncludeInferiors; @@ -3663,8 +4507,16 @@ main(int argc, char **argv) { root_height = DisplayHeight(dpy, scr); root_picture = XRenderCreatePicture(dpy, root, - XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)), - CPSubwindowMode, &pa); + XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)), + CPSubwindowMode, &pa); + if (opts.paint_on_overlay) { + tgt_picture = XRenderCreatePicture(dpy, overlay, + XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)), + CPSubwindowMode, &pa); + } + else { + tgt_picture = root_picture; + } black_picture = solid_picture(dpy, True, 1, 0, 0, 0); @@ -3712,31 +4564,39 @@ main(int argc, char **argv) { ufd.fd = ConnectionNumber(dpy); ufd.events = POLLIN; + if (opts.sw_opti) + paint_tm_offset = get_time_timespec().tv_nsec; + + reg_ignore_expire = True; + t = paint_preprocess(dpy, list); + paint_all(dpy, None, t); // Initialize idling idling = False; - for (;;) { - do { - if (!QLength(dpy)) { - if (poll(&ufd, 1, (idling ? -1: fade_timeout())) == 0) { - break; - } - } + // Main loop + while (1) { + Bool ev_received = False; + while (QLength(dpy) + || (evpoll(&ufd, + (ev_received ? 0: (idling ? -1: fade_timeout()))) > 0)) { XNextEvent(dpy, &ev); - ev_handle((XEvent *)&ev); - } while (QLength(dpy)); + ev_handle((XEvent *) &ev); + ev_received = True; + } // idling will be turned off during paint_preprocess() if needed idling = True; t = paint_preprocess(dpy, list); - if (all_damage) { + + if (all_damage && !is_region_empty(dpy, all_damage)) { static int paint; paint_all(dpy, all_damage, t); + reg_ignore_expire = False; paint++; XSync(dpy, False); all_damage = None; |