diff --git a/.circleci/config.yml b/.circleci/config.yml index 5d8af5ecd5..dcd8f58769 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,10 @@ -defaults: &defaults - docker: - - image: yshui/comptonci -cached-checkout: &ccheckout -just-build: &build +executors: + e: + docker: + - image: yshui/comptonci + working_directory: "/tmp/workspace" + environment: + UBSAN_OPTIONS: "halt_on_error=1" version: 2.1 commands: @@ -10,7 +12,7 @@ commands: parameters: build-config: type: string - default: + default: "" cc: type: string default: cc @@ -27,10 +29,10 @@ commands: - ".git" - run: name: config - command: CC=<< parameters.cc >> meson << parameters.build-config >> --werror . build + command: CC=<< parameters.cc >> meson << parameters.build-config >> -Dunittest=true --werror . build - run: name: build - command: ninja -C build + command: ninja -vC build test-xvfb: steps: - run: @@ -40,48 +42,72 @@ commands: jobs: basic: - <<: *defaults + executor: e steps: - build: - build-config: -Dbuild_docs=true + build-config: -Dbuild_docs=true -Db_coverage=true + - persist_to_workspace: + root: . + paths: + - . + test: + executor: e + steps: + - attach_workspace: + at: /tmp/workspace + - run: + name: unit test + command: ninja -vC build test + - run: + name: test config file parsing + command: xvfb-run -s "-screen 0 640x480x24" build/src/compton --config compton.sample.conf --vsync=none --diagnostics + - run: + name: generate coverage reports + command: cd build; find -name '*.gcno' -exec gcov -pb {} + + - run: + name: download codecov scripts + command: curl -s https://codecov.io/bash > codecov.sh + - run: + name: upload coverage reports + command: bash ./codecov.sh -X gcov + minimal: - <<: *defaults + executor: e steps: - build: build-config: -Dopengl=false -Ddbus=false -Dregex=false -Dconfig_file=false nogl: - <<: *defaults + executor: e steps: - build: build-config: -Dopengl=false noregex: - <<: *defaults + executor: e steps: - build: build-config: -Dregex=false clang_basic: - <<: *defaults + executor: e steps: - build: - cc: clang-6.0 - build-config: + cc: clang clang_minimal: - <<: *defaults + executor: e steps: - build: - cc: clang-6.0 + cc: clang build-config: -Dopengl=false -Ddbus=false -Dregex=false -Dconfig_file=false clang_nogl: - <<: *defaults + executor: e steps: - build: - cc: clang-6.0 + cc: clang build-config: -Dopengl=false clang_noregex: - <<: *defaults + executor: e steps: - build: - cc: clang-6.0 + cc: clang build-config: -Dregex=false workflows: @@ -93,6 +119,9 @@ workflows: - clang_minimal - nogl - clang_nogl + - test: + requires: + - basic # - test-xvfb # vim: set sw=2 ts=8 et: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..e997bc564a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "subprojects/test.h"] + path = subprojects/test.h + url = https://github.com/yshui/test.h diff --git a/README.md b/README.md index 7fb5fd3c18..e976187d66 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,21 @@ We also try to fix bugs. The original README can be found [here](README_orig.md) -## Changelog +## Call for testers + +### `--experimental-backends` + +This flag enables the refactored/partially rewritten backends. + +Currently new backends features better vsync with the xrender backend, improved input lag with the glx backend (for non-NVIDIA users). The performance should be on par with the old backends. + +New backend features will only be implemented on the new backends from now on, and the old backends will eventually be phased out after the new backends stabilizes. + +To test the new backends, add the `--experimental-backends` flag to the command line you used to run compton. This flag is not available from the configuration file. + +To report issues with the new backends, please state explicitly you are using the new backends in your report. + +## Change Log See [Releases](https://github.com/yshui/compton/releases) @@ -33,7 +47,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, meso * xcb-composite * xcb-image * xcb-present -* xcb-xinerama (optional, disable with the `-Dxinerama=false` meson configure flag) +* xcb-xinerama * pixman * libdbus (optional, disable with the `-Ddbus=false` meson configure flag) * libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag) @@ -41,18 +55,35 @@ Assuming you already have all the usual building tools installed (e.g. gcc, meso * libGL (optional, disable with the `-Dopengl=false` meson configure flag) * libpcre (optional, disable with the `-Dregex=false` meson configure flag) * libev +* uthash To build the documents, you need `asciidoc` ### To build ```bash +$ git submodule update --init --recursive $ meson --buildtype=release . build $ ninja -C build ``` Built binary can be found in `build/src` +If you have libraries and/or headers installed at non-default location (e.g. under `/usr/local/`), you might need to tell meson about them, since meson doesn't look for dependencies there by default. + +You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this: + +```bash +$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson --buildtype=release . build + +``` + +As an example, on FreeBSD, you might have to run meson with: +```bash +$ LDFLAGS="-L/usr/local/include" CPPFLAGS="-I/usr/local/include" meson --buildtype=release . build +$ ninja -C build +``` + ### To install ``` bash @@ -71,6 +102,8 @@ You can look at the [Projects](https://github.com/yshui/compton/projects) page, Even if you don't want to contribute code, you can still contribute by compiling and running this branch, and report any issue you can find. +Contributions to the documents and wiki will also be appreciated. + ## Contributors See [CONTRIBUTORS](CONTRIBUTORS) diff --git a/dbus-examples/cdbus-driver.sh b/dbus-examples/cdbus-driver.sh index 6215816f40..fd8741af4e 100755 --- a/dbus-examples/cdbus-driver.sh +++ b/dbus-examples/cdbus-driver.sh @@ -25,9 +25,6 @@ type_enum='uint16' # List all window ID compton manages (except destroyed ones) dbus-send --print-reply --dest="$service" "$object" "${interface}.list_win" -# Ensure we are tracking focus -dbus-send --print-reply --dest="$service" "$object" "${interface}.opts_set" string:track_focus boolean:true - # Get window ID of currently focused window focused=$(dbus-send --print-reply --dest="$service" "$object" "${interface}.find_win" string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p') diff --git a/dbus-examples/inverter.sh b/dbus-examples/inverter.sh index 8cb7d4669c..3513f92828 100755 --- a/dbus-examples/inverter.sh +++ b/dbus-examples/inverter.sh @@ -43,7 +43,6 @@ if [ -z "$1" -o "$1" = "selected" ]; then window=$(xwininfo -frame | sed -n 's/^xwininfo: Window id: \(0x[[:xdigit:]][[:xdigit:]]*\).*/\1/p') # Select window by mouse elif [ "$1" = "focused" ]; then # Ensure we are tracking focus - ${compton_dbus}opts_set string:track_focus boolean:true & window=$(${compton_dbus}find_win string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p') # Query compton for the active window elif echo "$1" | grep -Eiq '^([[:digit:]][[:digit:]]*|0x[[:xdigit:]][[:xdigit:]]*)$'; then window="$1" # Accept user-specified window-id if the format is correct diff --git a/man/compton.1.asciidoc b/man/compton.1.asciidoc index ec1439eeea..37d30e0755 100644 --- a/man/compton.1.asciidoc +++ b/man/compton.1.asciidoc @@ -71,7 +71,7 @@ OPTIONS Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers. *--log-level*:: - Set the log level. Possible values are "TRACE", "DEBUG", "INFO", "WARN", "ERROR", in increasing level of importance. Case doesn't matter. + Set the log level. Possible values are "TRACE", "DEBUG", "INFO", "WARN", "ERROR", in increasing level of importance. Case doesn't matter. If using the "TRACE" log level, it's better to log into a file using *--log-file*, since it can generate a huge stream of logs. *--log-file*:: Set the log file. If *--log-file* is never specified, logs will be written to stderr. Otherwise, logs will to written to the given file, though some of the early logs might still be written to the stderr. When setting this option from the config file, it is recommended to use an absolute path. @@ -169,6 +169,9 @@ OPTIONS *--detect-client-leader*:: Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if *--detect-transient* is enabled, too. +*--blur-method, --blur-size, --blur-deviation*:: + Parameters for background blurring, see the *BLUR* section for more information. + *--blur-background*:: Blur background of semi-transparent / ARGB windows. Bad in performance, with driver-dependent behavior. The name of the switch may change without prior notifications. @@ -187,9 +190,9 @@ WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5... + In other words, the matrix is formatted as a list of comma separated numbers. The first two numbers must be integers, which specify the width and height of the matrix. They must be odd numbers. Then, the following 'width * height - 1' numbers specifies the numbers in the matrix, row by row, excluding the center element. + -The elements are finite floating point numbers. The decimal pointer has to be '.' (a period), and scientific notation is not supported. +The elements are finite floating point numbers. The decimal pointer has to be '.' (a period), scientific notation is not supported. + -The element in the center will either be 1.0 or changing based on opacity, depending on whether you have `--blur-background-fixed`. Yet the automatic adjustment of blur factor may not work well with a custom blur kernel. +The element in the center will either be 1.0 or varying based on opacity, depending on whether you have `--blur-background-fixed`. Yet the automatic adjustment of blur factor may not work well with a custom blur kernel. + A 7x7 Gaussian blur kernel (sigma = 0.84089642) looks like: + @@ -232,7 +235,7 @@ May also be one of the predefined kernels: `3x3box` (default), `5x5box`, `7x7box *--glx-no-rebind-pixmap*:: GLX backend: Avoid rebinding pixmap on window damage. Probably could improve performance on rapid window content changes, but is known to break things on some drivers (LLVMpipe, xf86-video-intel, etc.). Recommended if it works. -*-use-damage*:: +*--use-damage*:: Use the damage information to limit rendering to parts of the screen that has actually changed. Potentially improves the performance. *--xrender-sync-fence*:: @@ -371,6 +374,35 @@ Following per window-type options are available: :: redir-ignore::: Controls whether this type of windows should cause screen to become redirected again after been unredirected. If you have *--unredir-if-possible* set, and doesn't want certain window to cause unnecessary screen redirection, you can set this to `true`. +BLUR +---- +You can configure how the window background is blurred using a 'blur' section in your configuration file. Here is an example: + +-------- +blur: +{ + method = "gaussian"; + size = 10; + deviation = 5.0; +}; +-------- + +Available options of the 'blur' section are: :: + + *method*::: + A string. Controls the blur method. Corresponds to the `--blur-method` command line option. Available choices are: + 'none' to disable blurring; 'gaussian' for gaussian blur; 'box' for box blur; 'kernel' for convolution blur with a custom kernel. + Note: 'gaussian' and 'box' blur methods are only supported by the experimental backends. + + *size*::: + An integer. The size of the blur kernel, required by 'gaussian' and 'box' blur methods. For the 'kernel' method, the size is included in the kernel. Corresponds to the `--blur-size` command line option. + + *deviation*::: + A floating point number. The standard deviation for the 'gaussian' blur method. Corresponds to the `--blur-deviation` command line option. + + *kernel*::: + A string. The kernel to use for the 'kernel' blur method, specified in the same format as the `--blur-kerns` option. Corresponds to the `--blur-kerns` command line option. + SIGNALS ------- diff --git a/meson.build b/meson.build index 7b30f68f96..a31b37089c 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('compton', 'c', version: '6', +project('compton', 'c', version: '7', default_options: ['c_std=c11']) cc = meson.get_compiler('c') @@ -31,6 +31,9 @@ if get_option('sanitize') endif add_global_arguments('-fsanitize='+','.join(sanitizers), language: 'c') add_global_link_arguments('-fsanitize='+','.join(sanitizers), language: 'c') + if cc.has_argument('-fno-sanitize=unsigned-integer-overflow') + add_global_arguments('-fno-sanitize=unsigned-integer-overflow', language: 'c') + endif endif if get_option('modularize') @@ -46,18 +49,25 @@ endif add_global_arguments('-D_GNU_SOURCE', language: 'c') -warns = [ 'all', 'extra', 'no-unused-parameter', 'nonnull', 'shadow', - 'implicit-fallthrough', 'no-unknown-warning-option', 'no-missing-braces' ] +if cc.has_header('stdc-predef.h') + add_global_arguments('-DHAS_STDC_PREDEF_H', language: 'c') +endif + +warns = [ 'all', 'extra', 'no-unused-parameter', 'nonnull', 'shadow', 'no-type-limits', + 'implicit-fallthrough', 'no-unknown-warning-option', 'no-missing-braces', 'conversion' ] foreach w : warns if cc.has_argument('-W'+w) add_global_arguments('-W'+w, language: 'c') endif endforeach +test_h_dep = subproject('test.h').get_variable('test_h_dep') + subdir('src') subdir('man') -install_subdir('bin', install_dir: '') +install_data(['bin/compton-convgen.py', 'bin/compton-trans'], + install_dir: get_option('bindir')) install_data('compton.desktop', install_dir: 'share/applications') install_data('media/icons/48x48/compton.png', install_dir: 'share/icons/hicolor/48x48/apps') diff --git a/meson_options.txt b/meson_options.txt index 95292d17cb..a999bf752f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -12,3 +12,5 @@ option('xrescheck', type: 'boolean', value: false, description: 'Enable X resour option('build_docs', type: 'boolean', value: false, description: 'Build documentation and man pages') option('modularize', type: 'boolean', value: false, description: 'Build with clang\'s module system') + +option('unittest', type: 'boolean', value: false, description: 'Enable unittests in the code') diff --git a/src/atom.c b/src/atom.c new file mode 100644 index 0000000000..4f622744a7 --- /dev/null +++ b/src/atom.c @@ -0,0 +1,34 @@ +#include + +#include "atom.h" +#include "common.h" +#include "utils.h" + +static inline void *atom_getter(void *ud, const char *atom_name, int *err) { + xcb_connection_t *c = ud; + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply( + c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL); + + xcb_atom_t atom = XCB_NONE; + if (reply) { + log_debug("Atom %s is %d", atom_name, reply->atom); + atom = reply->atom; + free(reply); + } else { + log_error("Failed to intern atoms"); + *err = 1; + } + return (void *)(intptr_t)atom; +} + +/** + * Create a new atom structure and fetch all predefined atoms + */ +struct atom *init_atoms(xcb_connection_t *c) { + auto atoms = ccalloc(1, struct atom); + atoms->c = new_cache((void *)c, atom_getter, NULL); +#define ATOM_GET(x) atoms->a##x = (xcb_atom_t)(intptr_t)cache_get(atoms->c, #x, NULL) + LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST); +#undef ATOM_GET + return atoms; +} diff --git a/src/atom.h b/src/atom.h new file mode 100644 index 0000000000..69a5e5cde2 --- /dev/null +++ b/src/atom.h @@ -0,0 +1,55 @@ +#pragma once +#include + +#include + +#include "meta.h" +#include "cache.h" + +// Splitted into 2 lists because of the limitation of our macros +#define ATOM_LIST \ + _NET_WM_WINDOW_OPACITY, \ + _NET_FRAME_EXTENTS, \ + WM_STATE, \ + _NET_WM_NAME, \ + _NET_WM_PID, \ + WM_NAME, \ + WM_CLASS, \ + WM_TRANSIENT_FOR, \ + WM_WINDOW_ROLE, \ + WM_CLIENT_LEADER, \ + _NET_ACTIVE_WINDOW, \ + _COMPTON_SHADOW, \ + _NET_WM_WINDOW_TYPE, \ + _NET_WM_WINDOW_TYPE_DESKTOP, \ + _NET_WM_WINDOW_TYPE_DOCK, \ + _NET_WM_WINDOW_TYPE_TOOLBAR, \ + _NET_WM_WINDOW_TYPE_MENU, \ + _NET_WM_WINDOW_TYPE_UTILITY, \ + _NET_WM_WINDOW_TYPE_SPLASH, \ + _NET_WM_WINDOW_TYPE_DIALOG, \ + _NET_WM_WINDOW_TYPE_NORMAL, \ + _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, \ + _NET_WM_WINDOW_TYPE_POPUP_MENU, \ + _NET_WM_WINDOW_TYPE_TOOLTIP, \ + _NET_WM_WINDOW_TYPE_NOTIFICATION, \ + _NET_WM_WINDOW_TYPE_COMBO, \ + _NET_WM_WINDOW_TYPE_DND + +#define ATOM_DEF(x) xcb_atom_t a##x + +struct atom { + struct cache *c; + LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST); +}; + +struct atom *init_atoms(xcb_connection_t *); + +static inline xcb_atom_t get_atom(struct atom *a, const char *key) { + return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL); +} + +static inline void destroy_atoms(struct atom *a) { + cache_free(a->c); + free(a); +} diff --git a/src/backend/backend.c b/src/backend/backend.c index cd83087e82..af98791561 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -39,7 +39,9 @@ region_t get_damage(session_t *ps, bool all_damage) { pixman_region32_copy(®ion, &ps->screen_reg); } else { for (int i = 0; i < buffer_age; i++) { - const int curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage; + auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage; + log_trace("damage index: %d, damage ring offset: %ld", i, curr); + dump_region(&ps->damage_ring[curr]); pixman_region32_union(®ion, ®ion, &ps->damage_ring[curr]); } pixman_region32_intersect(®ion, ®ion, &ps->screen_reg); @@ -48,7 +50,7 @@ region_t get_damage(session_t *ps, bool all_damage) { } /// paint all windows -void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { +void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // All painting will be limited to the damage, if _some_ of // the paints bleed out of the damage region, it will destroy // part of the image we want to reuse @@ -96,8 +98,9 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { // on top of that window. This is used to reduce the number of pixels painted. // // Whether this is beneficial is to be determined XXX - for (win *w = t; w; w = w->prev_trans) { + for (auto w = t; w; w = w->prev_trans) { pixman_region32_subtract(®_visible, &ps->screen_reg, w->reg_ignore); + assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR)); // The bounding shape of the window, in global/target coordinates // reminder: bounding shape contains the WM frame @@ -159,27 +162,38 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { pixman_region32_intersect(®_paint, ®_bound, ®_damage); // Blur window background - bool win_transparent = ps->backend_data->ops->is_image_transparent( - ps->backend_data, w->win_image); - bool frame_transparent = w->frame_opacity != 1; + // TODO since the background might change the content of the window (e.g. + // with shaders), we should consult the background whether the window + // is transparent or not. for now we will just rely on the + // force_win_blend option + auto real_win_mode = w->mode; + if (w->blur_background && - (win_transparent || (ps->o.blur_background_frame && frame_transparent))) { + (ps->o.force_win_blend || real_win_mode == WMODE_TRANS || + (ps->o.blur_background_frame && real_win_mode == WMODE_FRAME_TRANS))) { // Minimize the region we try to blur, if the window // itself is not opaque, only the frame is. // TODO resize blur region to fix black line artifact - if (!win_is_solid(ps, w)) { + if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) { // We need to blur the bounding shape of the window // (reg_paint = reg_bound \cap reg_damage) ps->backend_data->ops->blur(ps->backend_data, w->opacity, + ps->backend_blur_context, ®_paint, ®_visible); - } else if (frame_transparent && ps->o.blur_background_frame) { + } else { // Window itself is solid, we only need to blur the frame // region + + // Readability assertions + assert(ps->o.blur_background_frame); + assert(real_win_mode == WMODE_FRAME_TRANS); + auto reg_blur = win_get_region_frame_local_by_val(w); pixman_region32_translate(®_blur, w->g.x, w->g.y); // make sure reg_blur \in reg_damage pixman_region32_intersect(®_blur, ®_blur, ®_damage); ps->backend_data->ops->blur(ps->backend_data, w->opacity, + ps->backend_blur_context, ®_blur, ®_visible); pixman_region32_fini(®_blur); } @@ -256,7 +270,8 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { if (ps->o.monitor_repaint) { reg_damage = get_damage(ps, false); - ps->backend_data->ops->fill(ps->backend_data, 0.5, 0, 0, 0.5, ®_damage); + ps->backend_data->ops->fill(ps->backend_data, + (struct color){0.5, 0, 0, 0.5}, ®_damage); pixman_region32_fini(®_damage); } @@ -274,25 +289,15 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { } #ifdef DEBUG_REPAINT - print_timestamp(ps); 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); + log_trace("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec); last_paint = now; - printf("paint:"); + log_trace("paint:"); for (win *w = t; w; w = w->prev_trans) - printf(" %#010lx", w->id); - putchar('\n'); - fflush(stdout); + log_trace(" %#010lx", w->id); #endif - - // Check if fading is finished on all painted windows - win *pprev = NULL; - for (win *w = t; w; w = pprev) { - pprev = w->prev_trans; - win_check_fade_finished(ps, &w); - } } // vim: set noet sw=8 ts=8 : diff --git a/src/backend/backend.h b/src/backend/backend.h index 6c956786f6..f69ae8509b 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -6,22 +6,31 @@ #include #include "compiler.h" +#include "driver.h" #include "kernel.h" #include "region.h" +#include "types.h" #include "x.h" typedef struct session session_t; -typedef struct win win; +struct managed_win; +struct ev_loop; struct backend_operations; typedef struct backend_base { struct backend_operations *ops; xcb_connection_t *c; xcb_window_t root; + struct ev_loop *loop; + + /// Whether the backend can accept new render request at the moment + bool busy; // ... } backend_t; +typedef void (*backend_ready_callback_t)(void *); + enum image_operations { // Invert the color of the entire image, `reg_op` ignored IMAGE_OP_INVERT_COLOR_ALL, @@ -34,11 +43,33 @@ enum image_operations { IMAGE_OP_APPLY_ALPHA_ALL, // Change the effective size of the image, without touching the backing image // itself. When the image is used, the backing image should be tiled to fill its - // effective size. `reg_op` and `reg_visibile` is ignored. `arg` is two integers, + // effective size. `reg_op` and `reg_visible` is ignored. `arg` is two integers, // width and height, in that order. IMAGE_OP_RESIZE_TILE, }; +enum blur_method { + BLUR_METHOD_NONE = 0, + BLUR_METHOD_KERNEL, + BLUR_METHOD_BOX, + BLUR_METHOD_GAUSSIAN, + BLUR_METHOD_INVALID, +}; + +struct gaussian_blur_args { + int size; + double deviation; +}; + +struct box_blur_args { + int size; +}; + +struct kernel_blur_args { + struct conv **kernels; + int kernel_count; +}; + struct backend_operations { // =========== Initialization =========== @@ -48,7 +79,7 @@ struct backend_operations { /// 1) if ps->overlay is not XCB_NONE, use that /// 2) use ps->root otherwise /// TODO make the target window a parameter - backend_t *(*init)(session_t *) attr_nonnull(1); + backend_t *(*init)(session_t *)attr_nonnull(1); void (*deinit)(backend_t *backend_data) attr_nonnull(1); /// Called when rendering will be stopped for an unknown amount of @@ -72,6 +103,11 @@ struct backend_operations { // =========== Rendering ============ + // NOTE: general idea about reg_paint/reg_op vs reg_visible is that reg_visible is + // merely a hint. Ignoring reg_visible entirely don't affect the correctness of + // the operation performed. OTOH reg_paint/reg_op is part of the parameters of the + // operation, and must be honored in order to complete the operation correctly. + /// Called before when a new frame starts. /// /// Optional @@ -85,18 +121,18 @@ struct backend_operations { * @param image_data the image to paint * @param dst_x, dst_y the top left corner of the image in the target * @param reg_paint the clip region, in target coordinates - * @param reg_visibile the visible region, in target coordinates + * @param reg_visible the visible region, in target coordinates */ void (*compose)(backend_t *backend_data, void *image_data, int dst_x, int dst_y, const region_t *reg_paint, const region_t *reg_visible); /// Fill rectangle of target, mostly for debug purposes, optional. - void (*fill)(backend_t *backend_data, double r, double g, double b, double a, - const region_t *clip); + void (*fill)(backend_t *backend_data, struct color, const region_t *clip); /// Blur a given region of the target. - bool (*blur)(backend_t *backend_data, double opacity, const region_t *reg_blur, - const region_t *reg_visible) attr_nonnull(1, 3, 4); + bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, + const region_t *reg_blur, const region_t *reg_visible) + attr_nonnull(1, 3, 4, 5); /// Present the back buffer onto the screen. /// @@ -129,8 +165,7 @@ struct backend_operations { // want to break that assumption as for now. We need to reconsider this. /// Free resources associated with an image data structure - void (*release_image)(backend_t *backend_data, void *img_data) - attr_nonnull(1, 2); + void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2); // =========== Query =========== @@ -175,16 +210,24 @@ struct backend_operations { /// returned image should not affect the original image void *(*copy)(backend_t *base, const void *image_data, const region_t *reg_visible); + /// Create a blur context that can be used to call `blur` + void *(*create_blur_context)(backend_t *base, enum blur_method, void *args); + void (*destroy_blur_context)(backend_t *base, void *ctx); + // =========== Hooks ============ /// Let the backend hook into the event handling queue + void (*set_ready_callback)(backend_t *, backend_ready_callback_t cb); + /// Called right after compton has handled its events. + void (*handle_events)(backend_t *); + // =========== Misc ============ + /// Return the driver that is been used by the backend + enum driver (*detect_driver)(backend_t *backend_data); }; -typedef backend_t *(*backend_init_fn)(session_t *ps) attr_nonnull(1); +typedef backend_t *(*backend_init_fn)(session_t *ps)attr_nonnull(1); extern struct backend_operations *backend_list[]; -bool default_is_win_transparent(void *, win *, void *); -bool default_is_frame_transparent(void *, win *, void *); -void paint_all_new(session_t *ps, win *const t, bool ignore_damage) attr_nonnull(1); +void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage) + attr_nonnull(1); -// vim: set noet sw=8 ts=8 : diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 644dbe9d79..9561c9ae62 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -38,10 +38,10 @@ xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool a return XCB_NONE; } - col.alpha = a * 0xffff; - col.red = r * 0xffff; - col.green = g * 0xffff; - col.blue = b * 0xffff; + col.alpha = (uint16_t)(a * 0xffff); + col.red = (uint16_t)(r * 0xffff); + col.green = (uint16_t)(g * 0xffff); + col.blue = (uint16_t)(b * 0xffff); rect.x = 0; rect.y = 0; @@ -74,21 +74,22 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, assert(shadow_sum); // We only support square kernels for shadow assert(kernel->w == kernel->h); - int d = kernel->w, r = d / 2; + int d = kernel->w; + int r = d / 2; int swidth = width + r * 2, sheight = height + r * 2; assert(d % 2 == 1); assert(d > 0); - ximage = xcb_image_create_native(c, swidth, sheight, XCB_IMAGE_FORMAT_Z_PIXMAP, 8, - 0, 0, NULL); + ximage = xcb_image_create_native(c, to_u16_checked(swidth), to_u16_checked(sheight), + XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL); if (!ximage) { log_error("failed to create an X image"); return 0; } unsigned char *data = ximage->data; - uint32_t sstride = ximage->stride; + long sstride = ximage->stride; // If the window body is smaller than the kernel, we do convolution directly if (width < r * 2 && height < r * 2) { @@ -96,13 +97,14 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, for (int x = 0; x < swidth; x++) { double sum = sum_kernel_normalized( kernel, d - x - 1, d - y - 1, width, height); - data[y * sstride + x] = sum * 255.0; + data[y * sstride + x] = (uint8_t)(sum * 255.0); } } return ximage; } if (height < r * 2) { + // Implies width >= r * 2 // If the window height is smaller than the kernel, we divide // the window like this: // -r r width-r width+r @@ -114,14 +116,15 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, double sum = sum_kernel_normalized(kernel, d - x - 1, d - y - 1, d, height) * 255.0; - data[y * sstride + x] = sum; - data[y * sstride + swidth - x - 1] = sum; + data[y * sstride + x] = (uint8_t)sum; + data[y * sstride + swidth - x - 1] = (uint8_t)sum; } } for (int y = 0; y < sheight; y++) { double sum = sum_kernel_normalized(kernel, 0, d - y - 1, d, height) * 255.0; - memset(&data[y * sstride + r * 2], sum, width - 2 * r); + memset(&data[y * sstride + r * 2], (uint8_t)sum, + (size_t)(width - 2 * r)); } return ximage; } @@ -132,49 +135,52 @@ make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, double sum = sum_kernel_normalized(kernel, d - x - 1, d - y - 1, width, d) * 255.0; - data[y * sstride + x] = sum; - data[(sheight - y - 1) * sstride + x] = sum; + data[y * sstride + x] = (uint8_t)sum; + data[(sheight - y - 1) * sstride + x] = (uint8_t)sum; } } for (int x = 0; x < swidth; x++) { double sum = sum_kernel_normalized(kernel, d - x - 1, 0, width, d) * 255.0; for (int y = r * 2; y < height; y++) { - data[y * sstride + x] = sum; + data[y * sstride + x] = (uint8_t)sum; } } return ximage; } + // Implies: width >= r * 2 && height >= r * 2 + // Fill part 3 for (int y = r; y < height + r; y++) { - memset(data + sstride * y + r, 255 * opacity, width); + memset(data + sstride * y + r, (uint8_t)(255 * opacity), (size_t)width); } // Part 1 for (int y = 0; y < r * 2; y++) { for (int x = 0; x < r * 2; x++) { double tmpsum = shadow_sum[y * d + x] * opacity * 255.0; - data[y * sstride + x] = tmpsum; - data[(sheight - y - 1) * sstride + x] = tmpsum; - data[(sheight - y - 1) * sstride + (swidth - x - 1)] = tmpsum; - data[y * sstride + (swidth - x - 1)] = tmpsum; + data[y * sstride + x] = (uint8_t)tmpsum; + data[(sheight - y - 1) * sstride + x] = (uint8_t)tmpsum; + data[(sheight - y - 1) * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; + data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; } } // Part 2, top/bottom for (int y = 0; y < r * 2; y++) { double tmpsum = shadow_sum[d * y + d - 1] * opacity * 255.0; - memset(&data[y * sstride + r * 2], tmpsum, width - r * 2); - memset(&data[(sheight - y - 1) * sstride + r * 2], tmpsum, width - r * 2); + memset(&data[y * sstride + r * 2], (uint8_t)tmpsum, (size_t)(width - r * 2)); + memset(&data[(sheight - y - 1) * sstride + r * 2], (uint8_t)tmpsum, + (size_t)(width - r * 2)); } // Part 2, left/right for (int x = 0; x < r * 2; x++) { double tmpsum = shadow_sum[d * (d - 1) + x] * opacity * 255.0; for (int y = r * 2; y < height; y++) { - data[y * sstride + x] = tmpsum; - data[y * sstride + (swidth - x - 1)] = tmpsum; + data[y * sstride + x] = (uint8_t)tmpsum; + data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; } } @@ -215,7 +221,7 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i goto shadow_picture_err; } - gc = xcb_generate_id(c); + gc = x_new_id(c); xcb_create_gc(c, gc, shadow_pixmap, 0, NULL); xcb_image_put(c, shadow_pixmap, gc, shadow_image, 0, 0, 0); @@ -256,8 +262,9 @@ bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const i return false; } -void *default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a) { +void * +default_backend_render_shadow(backend_t *backend_data, int width, int height, + const conv *kernel, double r, double g, double b, double a) { xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, true, 1, r, g, b), shadow = XCB_NONE; @@ -273,10 +280,63 @@ void *default_backend_render_shadow(backend_t *backend_data, int width, int heig return ret; } -bool default_is_win_transparent(void *backend_data, win *w, void *win_data) { - return w->mode != WMODE_SOLID; +static struct conv ** +generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) { + int r = args->size * 2 + 1; + assert(r > 0); + auto ret = ccalloc(2, struct conv *); + ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); + ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); + ret[0]->w = r; + ret[0]->h = 1; + ret[1]->w = 1; + ret[1]->h = r; + for (int i = 0; i < r; i++) { + ret[0]->data[i] = 1; + ret[1]->data[i] = 1; + } + *kernel_count = 2; + return ret; +} + +static struct conv ** +generate_gaussian_blur_kernel(struct gaussian_blur_args *args, int *kernel_count) { + int r = args->size * 2 + 1; + assert(r > 0); + auto ret = ccalloc(2, struct conv *); + ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); + ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); + ret[0]->w = r; + ret[0]->h = 1; + ret[1]->w = 1; + ret[1]->h = r; + for (int i = 0; i <= args->size; i++) { + ret[0]->data[i] = ret[0]->data[r - i - 1] = + 1.0 / (sqrt(2.0 * M_PI) * args->deviation) * + exp(-(args->size - i) * (args->size - i) / + (2 * args->deviation * args->deviation)); + ret[1]->data[i] = ret[1]->data[r - i - 1] = ret[0]->data[i]; + } + *kernel_count = 2; + return ret; +} + +/// Generate blur kernels for gaussian and box blur methods. Generated kernel is not +/// normalized, and the center element will always be 1. +struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count) { + switch (method) { + case BLUR_METHOD_BOX: return generate_box_blur_kernel(args, kernel_count); + case BLUR_METHOD_GAUSSIAN: + return generate_gaussian_blur_kernel(args, kernel_count); + default: break; + } + return NULL; } -bool default_is_frame_transparent(void *backend_data, win *w, void *win_data) { - return w->frame_opacity != 1; +void init_backend_base(struct backend_base *base, session_t *ps) { + base->c = ps->c; + base->loop = ps->loop; + base->root = ps->root; + base->busy = false; + base->ops = NULL; } diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index d0e9136e2f..28048b1cdb 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -7,14 +7,17 @@ #include +#include "config.h" #include "region.h" typedef struct session session_t; typedef struct win win; typedef struct conv conv; +typedef struct backend_base backend_t; +struct backend_operations; -bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, const int width, - const int height, const conv *kernel, xcb_render_picture_t shadow_pixel, +bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width, + int height, const conv *kernel, xcb_render_picture_t shadow_pixel, xcb_pixmap_t *pixmap, xcb_render_picture_t *pict); xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb, @@ -31,5 +34,10 @@ bool default_is_win_transparent(void *, win *, void *); /// caveat as `default_is_win_transparent` applies. bool default_is_frame_transparent(void *, win *, void *); -void *default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a); +void * +default_backend_render_shadow(backend_t *backend_data, int width, int height, + const conv *kernel, double r, double g, double b, double a); + +void init_backend_base(struct backend_base *base, session_t *ps); + +struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count); diff --git a/src/backend/driver.c b/src/backend/driver.c new file mode 100644 index 0000000000..cea58f9318 --- /dev/null +++ b/src/backend/driver.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#include +#include + +#include "backend/backend.h" +#include "backend/driver.h" +#include "common.h" + +enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) { + enum driver ret = 0; + // First we try doing backend agnostic detection using RANDR + // There's no way to query the X server about what driver is loaded, so RANDR is + // our best shot. + auto randr_version = xcb_randr_query_version_reply( + c, xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), + NULL); + if (randr_version && + (randr_version->major_version > 1 || randr_version->minor_version >= 4)) { + auto r = xcb_randr_get_providers_reply( + c, xcb_randr_get_providers(c, window), NULL); + if (r == NULL) { + log_warn("Failed to get RANDR providers"); + return 0; + } + + auto providers = xcb_randr_get_providers_providers(r); + for (auto i = 0; i < xcb_randr_get_providers_providers_length(r); i++) { + auto r2 = xcb_randr_get_provider_info_reply( + c, xcb_randr_get_provider_info(c, providers[i], r->timestamp), NULL); + if (r2 == NULL) { + continue; + } + if (r2->num_outputs == 0) { + free(r2); + continue; + } + + auto name_len = xcb_randr_get_provider_info_name_length(r2); + assert(name_len >= 0); + auto name = + strndup(xcb_randr_get_provider_info_name(r2), (size_t)name_len); + if (strcasestr(name, "modesetting") != NULL) { + ret |= DRIVER_MODESETTING; + } else if (strcasestr(name, "Radeon") != NULL) { + // Be conservative, add both radeon drivers + ret |= DRIVER_AMDGPU | DRIVER_RADEON; + } else if (strcasestr(name, "NVIDIA") != NULL) { + ret |= DRIVER_NVIDIA; + } else if (strcasestr(name, "nouveau") != NULL) { + ret |= DRIVER_NOUVEAU; + } else if (strcasestr(name, "Intel") != NULL) { + ret |= DRIVER_INTEL; + } + free(name); + } + free(r); + } + + // If the backend supports driver detection, use that as well + if (backend_data && backend_data->ops->detect_driver) { + ret |= backend_data->ops->detect_driver(backend_data); + } + return ret; +} diff --git a/src/backend/driver.h b/src/backend/driver.h new file mode 100644 index 0000000000..a575b1a773 --- /dev/null +++ b/src/backend/driver.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#pragma once + +#include +#include + +struct session; +struct backend_base; + +/// A list of possible drivers. +/// The driver situation is a bit complicated. There are two drivers we care about: the +/// DDX, and the OpenGL driver. They are usually paired, but not always, since there is +/// also the generic modesetting driver. +/// This enum represents _both_ drivers. +enum driver { + DRIVER_AMDGPU = 1, // AMDGPU for DDX, radeonsi for OpenGL + DRIVER_RADEON = 2, // ATI for DDX, mesa r600 for OpenGL + DRIVER_FGLRX = 4, + DRIVER_NVIDIA = 8, + DRIVER_NOUVEAU = 16, + DRIVER_INTEL = 32, + DRIVER_MODESETTING = 64, +}; + +/// Return a list of all drivers currently in use by the X server. +/// Note, this is a best-effort test, so no guarantee all drivers will be detected. +enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t); + +// Print driver names to stdout, for diagnostics +static inline void print_drivers(enum driver drivers) { + if (drivers & DRIVER_AMDGPU) { + printf("AMDGPU, "); + } + if (drivers & DRIVER_RADEON) { + printf("Radeon, "); + } + if (drivers & DRIVER_FGLRX) { + printf("fglrx, "); + } + if (drivers & DRIVER_NVIDIA) { + printf("NVIDIA, "); + } + if (drivers & DRIVER_NOUVEAU) { + printf("nouveau, "); + } + if (drivers & DRIVER_INTEL) { + printf("Intel, "); + } + if (drivers & DRIVER_MODESETTING) { + printf("modesetting, "); + } + printf("\b\b \n"); +} diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 18f21fa8b2..101f6b149f 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -17,11 +17,44 @@ #include "string_utils.h" #include "utils.h" +#include "backend/backend_common.h" #include "backend/gl/gl_common.h" #define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__ #define QUOTE(...) #__VA_ARGS__ +static const GLuint vert_coord_loc = 0; +static const GLuint vert_in_texcoord_loc = 1; + +struct gl_blur_context { + enum blur_method method; + gl_blur_shader_t *blur_shader; + + /// Temporary textures used for blurring. They are always the same size as the + /// target, so they are always big enough without resizing. + /// Turns out calling glTexImage to resize is expensive, so we avoid that. + GLuint blur_texture[2]; + /// Temporary fbo used for blurring + GLuint blur_fbo; + + int texture_width, texture_height; + + /// How much do we need to resize the damaged region for blurring. + int resize_width, resize_height; + + int npasses; +}; + +static GLint glGetUniformLocationChecked(GLuint p, const char *name) { + auto ret = glGetUniformLocation(p, name); + if (ret < 0) { + log_error("Failed to get location of uniform '%s'. compton might not " + "work correctly.", + name); + } + return ret; +} + GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { log_trace("===\n%s\n===", shader_str); @@ -119,14 +152,16 @@ GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_ { GLuint shaders[2]; - unsigned int count = 0; - if (vert_shader) + int count = 0; + if (vert_shader) { shaders[count++] = vert_shader; - if (frag_shader) + } + if (frag_shader) { shaders[count++] = frag_shader; - assert(count <= sizeof(shaders) / sizeof(shaders[0])); - if (count) + } + if (count) { prog = gl_create_program(shaders, count); + } } if (vert_shader) @@ -150,247 +185,357 @@ static void gl_free_prog_main(gl_win_shader_t *pprogram) { * Render a region with texture data. * * @param ptex the texture + * @param target the framebuffer to render into * @param dst_x,dst_y the top left corner of region where this texture - * should go. In Xorg coordinate system (important!). - * @param reg_tgt the clip region, also in Xorg coordinate system + * should go. In OpenGL coordinate system (important!). + * @param reg_tgt the clip region, in Xorg coordinate system * @param reg_visible ignored */ -void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, - const region_t *reg_tgt, const region_t *reg_visible) { +static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target, + GLint *coord, GLuint *indices, int nrects) { - gl_texture_t *ptex = image_data; struct gl_data *gd = (void *)base; - - // Until we start to use glClipControl, reg_tgt, dst_x and dst_y and - // in a different coordinate system than the one OpenGL uses. - // OpenGL window coordinate (or NDC) has the origin at the lower left of the - // screen, with y axis pointing up; Xorg has the origin at the upper left of the - // screen, with y axis pointing down. We have to do some coordinate conversion in - // this function - if (!ptex || !ptex->texture) { + if (!img || !img->inner->texture) { log_error("Missing texture."); return; } - // dst_y is the top coordinate, in OpenGL, it is the upper bound of the y - // coordinate. - dst_y = gd->height - dst_y; - auto dst_y2 = dst_y - ptex->height; - bool dual_texture = false; - // It's required by legacy versions of OpenGL to enable texture target - // before specifying environment. Thanks to madsy for telling me. - glEnable(GL_TEXTURE_2D); - if (gd->win_shader.prog) { - glUseProgram(gd->win_shader.prog); - if (gd->win_shader.unifm_opacity >= 0) { - glUniform1f(gd->win_shader.unifm_opacity, ptex->opacity); - } - if (gd->win_shader.unifm_invert_color >= 0) { - glUniform1i(gd->win_shader.unifm_invert_color, ptex->color_inverted); - } - if (gd->win_shader.unifm_tex >= 0) { - glUniform1i(gd->win_shader.unifm_tex, 0); - } - if (gd->win_shader.unifm_dim >= 0) { - glUniform1f(gd->win_shader.unifm_dim, ptex->dim); - } + assert(gd->win_shader.prog); + glUseProgram(gd->win_shader.prog); + if (gd->win_shader.unifm_opacity >= 0) { + glUniform1f(gd->win_shader.unifm_opacity, (float)img->opacity); + } + if (gd->win_shader.unifm_invert_color >= 0) { + glUniform1i(gd->win_shader.unifm_invert_color, img->color_inverted); + } + if (gd->win_shader.unifm_tex >= 0) { + glUniform1i(gd->win_shader.unifm_tex, 0); + } + if (gd->win_shader.unifm_dim >= 0) { + glUniform1f(gd->win_shader.unifm_dim, (float)img->dim); } // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", // x, y, width, height, dx, dy, ptex->width, ptex->height, z); // Bind texture - glBindTexture(GL_TEXTURE_2D, ptex->texture); + glBindTexture(GL_TEXTURE_2D, img->inner->texture); if (dual_texture) { glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, ptex->texture); + glBindTexture(GL_TEXTURE_2D, img->inner->texture); glActiveTexture(GL_TEXTURE0); } - // Painting - int nrects; + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint bo[2]; + glGenBuffers(2, bo); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, + indices, GL_STATIC_DRAW); + + glEnableVertexAttribArray(vert_coord_loc); + glEnableVertexAttribArray(vert_in_texcoord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); + glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); + glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); + glDisableVertexAttribArray(vert_coord_loc); + glDisableVertexAttribArray(vert_in_texcoord_loc); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); - const rect_t *rects; - rects = pixman_region32_rectangles((region_t *)reg_tgt, &nrects); + // Cleanup + glBindTexture(GL_TEXTURE_2D, 0); + + if (dual_texture) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(2, bo); + + glUseProgram(0); + + gl_check_err(); + + return; +} + +/// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates +/// @param[in] nrects, rects rectangles +/// @param[in] dst_x, dst_y origin of the OpenGL texture, affect the calculated texture +/// coordinates +/// @param[in] width, height size of the OpenGL texture +/// @param[in] root_height height of the back buffer +/// @param[in] y_inverted whether the texture is y inverted +/// @param[out] coord, indices output +static void x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, + int width, int height, int root_height, bool y_inverted, + GLint *coord, GLuint *indices) { + dst_y = root_height - dst_y; + if (y_inverted) { + dst_y -= height; + } - glBegin(GL_QUADS); - for (int ri = 0; ri < nrects; ++ri) { + for (int i = 0; i < nrects; i++) { // Y-flip. Note after this, crect.y1 > crect.y2 - rect_t crect = rects[ri]; - crect.y1 = gd->height - crect.y1; - crect.y2 = gd->height - crect.y2; + rect_t crect = rects[i]; + crect.y1 = root_height - crect.y1; + crect.y2 = root_height - crect.y2; // Calculate texture coordinates // (texture_x1, texture_y1), texture coord for the _bottom left_ corner - GLfloat texture_x1 = crect.x1 - dst_x; - GLfloat texture_y1 = crect.y2 - dst_y2; - GLfloat texture_x2 = texture_x1 + crect.x2 - crect.x1; - GLfloat texture_y2 = texture_y1 + crect.y1 - crect.y2; + GLint texture_x1 = crect.x1 - dst_x, texture_y1 = crect.y2 - dst_y, + texture_x2 = texture_x1 + (crect.x2 - crect.x1), + texture_y2 = texture_y1 + (crect.y1 - crect.y2); // X pixmaps might be Y inverted, invert the texture coordinates - if (ptex->y_inverted) { - texture_y1 = ptex->height - texture_y1; - texture_y2 = ptex->height - texture_y2; + if (y_inverted) { + texture_y1 = height - texture_y1; + texture_y2 = height - texture_y2; } - // GL_TEXTURE_2D coordinates are normalized - // TODO use texelFetch - texture_x1 /= ptex->width; - texture_y1 /= ptex->height; - texture_x2 /= ptex->width; - texture_y2 /= ptex->height; - // Vertex coordinates - GLint vx1 = crect.x1; - GLint vy1 = crect.y2; - GLint vx2 = crect.x2; - GLint vy2 = crect.y1; + auto vx1 = crect.x1; + auto vy1 = crect.y2; + auto vx2 = crect.x2; + auto vy2 = crect.y1; // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", // ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); - GLfloat texture_x[] = {texture_x1, texture_x2, texture_x2, texture_x1}; - GLfloat texture_y[] = {texture_y1, texture_y1, texture_y2, texture_y2}; - GLint vx[] = {vx1, vx2, vx2, vx1}; - GLint vy[] = {vy1, vy1, vy2, vy2}; - - for (int i = 0; i < 4; i++) { - glTexCoord2f(texture_x[i], texture_y[i]); - glVertex3i(vx[i], vy[i], 0); - } + memcpy(&coord[i * 16], + (GLint[][2]){ + {vx1, vy1}, + {texture_x1, texture_y1}, + {vx2, vy1}, + {texture_x2, texture_y1}, + {vx2, vy2}, + {texture_x2, texture_y2}, + {vx1, vy2}, + {texture_x1, texture_y2}, + }, + sizeof(GLint[2]) * 8); + + GLuint u = (GLuint)(i * 4); + memcpy(&indices[i * 6], (GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}, + sizeof(GLuint) * 6); } +} - glEnd(); - - // Cleanup - glBindTexture(GL_TEXTURE_2D, 0); - glColor4f(0.0f, 0.0f, 0.0f, 0.0f); - glDisable(GL_COLOR_LOGIC_OP); - glDisable(GL_TEXTURE_2D); +void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, + const region_t *reg_tgt, const region_t *reg_visible) { + struct gl_data *gd = (void *)base; + struct gl_image *img = image_data; - if (dual_texture) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); + // Painting + int nrects; + const rect_t *rects; + rects = pixman_region32_rectangles((region_t *)reg_tgt, &nrects); + if (!nrects) { + // Nothing to paint + return; } - glUseProgram(0); + // Until we start to use glClipControl, reg_tgt, dst_x and dst_y and + // in a different coordinate system than the one OpenGL uses. + // OpenGL window coordinate (or NDC) has the origin at the lower left of the + // screen, with y axis pointing up; Xorg has the origin at the upper left of the + // screen, with y axis pointing down. We have to do some coordinate conversion in + // this function - gl_check_err(); + auto coord = ccalloc(nrects * 16, GLint); + auto indices = ccalloc(nrects * 6, GLuint); + x_rect_to_coords(nrects, rects, dst_x, dst_y, img->inner->width, img->inner->height, + gd->height, img->inner->y_inverted, coord, indices); + _gl_compose(base, img, 0, coord, indices, nrects); - return; + free(indices); + free(coord); } /** * Blur contents in a particular region. */ -bool gl_blur(backend_t *base, double opacity, const region_t *reg_blur, +bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blur, const region_t *reg_visible) { - // Remainder: regions are in Xorg coordinates + struct gl_blur_context *bctx = ctx; struct gl_data *gd = (void *)base; - const rect_t *extent = pixman_region32_extents((region_t *)reg_blur); - int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1; - int dst_y = gd->height - extent->y2; + + if (gd->width + bctx->resize_width * 2 != bctx->texture_width || + gd->height + bctx->resize_height * 2 != bctx->texture_height) { + // Resize the temporary textures used for blur in case the root + // size changed + bctx->texture_width = gd->width + bctx->resize_width * 2; + bctx->texture_height = gd->height + bctx->resize_height * 2; + + glBindTexture(GL_TEXTURE_2D, bctx->blur_texture[0]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bctx->texture_width, + bctx->texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + if (bctx->npasses > 1) { + glBindTexture(GL_TEXTURE_2D, bctx->blur_texture[1]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bctx->texture_width, + bctx->texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + } + + // XXX: do we need projection matrix for blur at all? + // Note: OpenGL matrices are column major + GLfloat projection_matrix[4][4] = { + {2.0f / (GLfloat)bctx->texture_width, 0, 0, 0}, + {0, 2.0f / (GLfloat)bctx->texture_height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + // Update projection matrices in the blur shaders + for (int i = 0; i < bctx->npasses - 1; i++) { + assert(bctx->blur_shader[i].prog); + glUseProgram(bctx->blur_shader[i].prog); + int pml = glGetUniformLocationChecked(bctx->blur_shader[i].prog, + "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + } + + GLfloat projection_matrix2[4][4] = {{2.0f / (GLfloat)gd->width, 0, 0, 0}, + {0, 2.0f / (GLfloat)gd->height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + assert(bctx->blur_shader[bctx->npasses - 1].prog); + glUseProgram(bctx->blur_shader[bctx->npasses - 1].prog); + int pml = glGetUniformLocationChecked( + bctx->blur_shader[bctx->npasses - 1].prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix2[0]); + } + + // Remainder: regions are in Xorg coordinates + auto reg_blur_resized = + resize_region(reg_blur, bctx->resize_width, bctx->resize_height); + const rect_t *extent = pixman_region32_extents((region_t *)reg_blur), + *extent_resized = pixman_region32_extents(®_blur_resized); + int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1, + width_resized = extent_resized->x2 - extent_resized->x1, + height_resized = extent_resized->y2 - extent_resized->y1; + int dst_y_resized_screen_coord = gd->height - extent_resized->y2, + dst_y_resized_fb_coord = bctx->texture_height - extent_resized->y2; if (width == 0 || height == 0) { return true; } - // these should be arguments bool ret = false; + int nrects, nrects_resized; + const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), + *rects_resized = + pixman_region32_rectangles(®_blur_resized, &nrects_resized); + if (!nrects || !nrects_resized) { + return true; + } + + auto coord = ccalloc(nrects * 16, GLint); + auto indices = ccalloc(nrects * 6, GLuint); + x_rect_to_coords(nrects, rects, extent_resized->x1, extent_resized->y2, + bctx->texture_width, bctx->texture_height, gd->height, false, + coord, indices); + + auto coord_resized = ccalloc(nrects_resized * 16, GLint); + auto indices_resized = ccalloc(nrects_resized * 6, GLuint); + x_rect_to_coords(nrects_resized, rects_resized, extent_resized->x1, + extent_resized->y2, bctx->texture_width, bctx->texture_height, + bctx->texture_height, false, coord_resized, indices_resized); + pixman_region32_fini(®_blur_resized); + + GLuint vao[2]; + glGenVertexArrays(2, vao); + GLuint bo[4]; + glGenBuffers(4, bo); + + glBindVertexArray(vao[0]); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, + indices, GL_STATIC_DRAW); + glEnableVertexAttribArray(vert_coord_loc); + glEnableVertexAttribArray(vert_in_texcoord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); + glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLfloat) * 4, (void *)(sizeof(GLint) * 2)); + + glBindVertexArray(vao[1]); + glBindBuffer(GL_ARRAY_BUFFER, bo[2]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16, + coord_resized, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized, + GL_STATIC_DRAW); + glEnableVertexAttribArray(vert_coord_loc); + glEnableVertexAttribArray(vert_in_texcoord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); + glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLfloat) * 4, (void *)(sizeof(GLint) * 2)); int curr = 0; glReadBuffer(GL_BACK); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, gd->blur_texture[0]); + glBindTexture(GL_TEXTURE_2D, bctx->blur_texture[0]); // Copy the area to be blurred into tmp buffer - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, extent->x1, dst_y, width, height); - for (int i = 0; i < gd->npasses; ++i) { - assert(i < MAX_BLUR_PASS - 1); - const gl_blur_shader_t *p = &gd->blur_shader[i]; + int copy_tex_xoffset = 0, copy_tex_yoffset = 0, copy_tex_x = extent_resized->x1, + copy_tex_y = dst_y_resized_screen_coord; + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, copy_tex_xoffset, copy_tex_yoffset, + copy_tex_x, copy_tex_y, width_resized, height_resized); + + for (int i = 0; i < bctx->npasses; ++i) { + const gl_blur_shader_t *p = &bctx->blur_shader[i]; assert(p->prog); - assert(gd->blur_texture[curr]); - glBindTexture(GL_TEXTURE_2D, gd->blur_texture[curr]); + assert(bctx->blur_texture[curr]); + glBindTexture(GL_TEXTURE_2D, bctx->blur_texture[curr]); glUseProgram(p->prog); - if (i < gd->npasses - 1) { - // not last pass, draw into framebuffer - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->blur_fbo); + if (i < bctx->npasses - 1) { + // not last pass, draw into framebuffer, with resized regions + glBindVertexArray(vao[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbo); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, gd->blur_texture[!curr], 0); + GL_TEXTURE_2D, bctx->blur_texture[!curr], 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { log_error("Framebuffer attachment failed."); goto end; } glUniform1f(p->unifm_opacity, 1.0); + // For other than last pass, we are drawing to a texture, we + // translate the render origin so we don't need a big texture + glUniform2f(p->orig_loc, -(GLfloat)extent_resized->x1, + -(GLfloat)dst_y_resized_fb_coord); + glViewport(0, 0, bctx->texture_width, bctx->texture_height); } else { - // last pass, draw directly into the back buffer + // last pass, draw directly into the back buffer, with origin + // regions + glBindVertexArray(vao[0]); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDrawBuffer(GL_BACK); - glUniform1f(p->unifm_opacity, opacity); + glUniform1f(p->unifm_opacity, (float)opacity); + glUniform2f(p->orig_loc, 0, 0); + glViewport(0, 0, gd->width, gd->height); } - glUniform1f(p->unifm_offset_x, 1.0 / gd->width); - glUniform1f(p->unifm_offset_y, 1.0 / gd->height); + glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); // XXX use multiple draw calls is probably going to be slow than // just simply blur the whole area. - int nrects; - const rect_t *rect = - pixman_region32_rectangles((region_t *)reg_blur, &nrects); - glBegin(GL_QUADS); - for (int j = 0; j < nrects; j++) { - rect_t crect = rect[j]; - // flip y axis, because the regions are in Xorg's coordinates, - // which is y-flipped from OpenGL's. - crect.y1 = gd->height - crect.y1; - crect.y2 = gd->height - crect.y2; - - // Texture coordinates - GLfloat texture_x1 = (crect.x1 - extent->x1); - GLfloat texture_y1 = (crect.y2 - dst_y); - GLfloat texture_x2 = texture_x1 + (crect.x2 - crect.x1); - GLfloat texture_y2 = texture_y1 + (crect.y1 - crect.y2); - - texture_x1 /= gd->width; - texture_x2 /= gd->width; - texture_y1 /= gd->height; - texture_y2 /= gd->height; - - // Vertex coordinates - // For passes before the last one, we are drawing into a buffer, - // so (dx, dy) from source maps to (0, 0) - GLfloat vx1 = crect.x1 - extent->x1; - GLfloat vy1 = crect.y2 - dst_y; - if (i == gd->npasses - 1) { - // For last pass, we are drawing back to source, so we - // don't need to map - vx1 = crect.x1; - vy1 = crect.y2; - } - GLfloat vx2 = vx1 + (crect.x2 - crect.x1); - GLfloat vy2 = vy1 + (crect.y1 - crect.y2); - - GLfloat texture_x[] = {texture_x1, texture_x2, texture_x2, texture_x1}; - GLfloat texture_y[] = {texture_y1, texture_y1, texture_y2, texture_y2}; - GLint vx[] = {vx1, vx2, vx2, vx1}; - GLint vy[] = {vy1, vy1, vy2, vy2}; - - for (int k = 0; k < 4; k++) { - glTexCoord2f(texture_x[k], texture_y[k]); - glVertex3i(vx[k], vy[k], 0); - } - } - glEnd(); - - glUseProgram(0); curr = !curr; } @@ -399,22 +544,35 @@ bool gl_blur(backend_t *base, double opacity, const region_t *reg_blur, end: glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(4, bo); + glBindVertexArray(0); + glDeleteVertexArrays(2, vao); + + free(indices); + free(coord); + free(indices_resized); + free(coord_resized); gl_check_err(); return ret; } -static GLuint glGetUniformLocationChecked(GLuint p, const char *name) { - auto ret = glGetUniformLocation(p, name); - if (ret < 0) { - log_error("Failed to get location of uniform '%s'. compton might not " - "work correctly.", - name); +// clang-format off +const char *vertex_shader = GLSL(330, + uniform mat4 projection; + uniform vec2 orig; + layout(location = 0) in vec2 coord; + layout(location = 1) in vec2 in_texcoord; + out vec2 texcoord; + void main() { + gl_Position = projection * vec4(coord + orig, 0, 1); + texcoord = in_texcoord; } - return ret; -} +); +// clang-format on /** * Load a GLSL main program from shader strings. @@ -434,6 +592,10 @@ static int gl_win_shader_from_string(const char *vshader_str, const char *fshade ret->unifm_tex = glGetUniformLocationChecked(ret->prog, "tex"); ret->unifm_dim = glGetUniformLocationChecked(ret->prog, "dim"); + glUseProgram(ret->prog); + int orig_loc = glGetUniformLocation(ret->prog, "orig"); + glUniform2f(orig_loc, 0, 0); + gl_check_err(); return true; @@ -444,63 +606,197 @@ static int gl_win_shader_from_string(const char *vshader_str, const char *fshade */ void gl_resize(struct gl_data *gd, int width, int height) { glViewport(0, 0, width, height); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, width, 0, height, -1000.0, 1000.0); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); gd->height = height; gd->width = width; - if (gd->npasses > 0) { - // Resize the temporary textures used for blur - glBindTexture(GL_TEXTURE_2D, gd->blur_texture[0]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, gd->width, gd->height, 0, - GL_BGRA, GL_UNSIGNED_BYTE, NULL); - if (gd->npasses > 1) { - glBindTexture(GL_TEXTURE_2D, gd->blur_texture[1]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, gd->width, gd->height, 0, - GL_BGRA, GL_UNSIGNED_BYTE, NULL); - } - } + // XXX: do we need projection matrix at all? + // Note: OpenGL matrices are column major + GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)width, 0, 0, 0}, + {0, 2.0f / (GLfloat)height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + // Update projection matrix in the win shader + glUseProgram(gd->win_shader.prog); + int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + + glUseProgram(gd->fill_shader.prog); + pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + + gl_check_err(); } -void gl_fill(backend_t *base, double r, double g, double b, double a, const region_t *clip) { +// clang-format off +static const char fill_frag[] = GLSL(330, + uniform vec4 color; + void main() { + gl_FragColor = color; + } +); + +static const char fill_vert[] = GLSL(330, + layout(location = 0) in vec2 in_coord; + uniform mat4 projection; + void main() { + gl_Position = projection * vec4(in_coord, 0, 1); + } +); +// clang-format on + +/// Fill a given region in bound framebuffer. +/// @param[in] y_inverted whether the y coordinates in `clip` should be inverted +static void +_gl_fill(backend_t *base, struct color c, const region_t *clip, int height, bool y_inverted) { + static const GLuint fill_vert_in_coord_loc = 0; int nrects; const rect_t *rect = pixman_region32_rectangles((region_t *)clip, &nrects); struct gl_data *gd = (void *)base; - glColor4f(r, g, b, a); - glBegin(GL_QUADS); + + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint bo[2]; + glGenBuffers(2, bo); + glUseProgram(gd->fill_shader.prog); + glUniform4f(gd->fill_shader.color_loc, (GLfloat)c.red, (GLfloat)c.green, + (GLfloat)c.blue, (GLfloat)c.alpha); + glEnableVertexAttribArray(fill_vert_in_coord_loc); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + + auto coord = ccalloc(nrects * 8, GLint); + auto indices = ccalloc(nrects * 6, GLuint); for (int i = 0; i < nrects; i++) { - glVertex2f(rect[i].x1, gd->height - rect[i].y2); - glVertex2f(rect[i].x2, gd->height - rect[i].y2); - glVertex2f(rect[i].x2, gd->height - rect[i].y1); - glVertex2f(rect[i].x1, gd->height - rect[i].y1); + GLint y1 = y_inverted ? height - rect[i].y2 : rect[i].y1, + y2 = y_inverted ? height - rect[i].y1 : rect[i].y2; + memcpy(&coord[i * 8], + (GLint[][2]){ + {rect[i].x1, y1}, {rect[i].x2, y1}, {rect[i].x2, y2}, {rect[i].x1, y2}}, + sizeof(GLint[2]) * 4); + indices[i * 6 + 0] = (GLuint)i * 4 + 0; + indices[i * 6 + 1] = (GLuint)i * 4 + 1; + indices[i * 6 + 2] = (GLuint)i * 4 + 2; + indices[i * 6 + 3] = (GLuint)i * 4 + 2; + indices[i * 6 + 4] = (GLuint)i * 4 + 3; + indices[i * 6 + 5] = (GLuint)i * 4 + 0; + } + glBufferData(GL_ARRAY_BUFFER, nrects * 8 * (long)sizeof(*coord), coord, GL_STREAM_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, nrects * 6 * (long)sizeof(*indices), + indices, GL_STREAM_DRAW); + + glVertexAttribPointer(fill_vert_in_coord_loc, 2, GL_INT, GL_FALSE, + sizeof(*coord) * 2, (void *)0); + glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDisableVertexAttribArray(fill_vert_in_coord_loc); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + + glDeleteBuffers(2, bo); +} + +void gl_fill(backend_t *base, struct color c, const region_t *clip) { + struct gl_data *gd = (void *)base; + return _gl_fill(base, c, clip, gd->height, true); +} + +void gl_release_image(backend_t *base, void *image_data) { + struct gl_image *wd = image_data; + struct gl_data *gl = (void *)base; + wd->inner->refcount--; + assert(wd->inner->refcount >= 0); + if (wd->inner->refcount > 0) { + free(wd); + return; + } + + gl->release_user_data(base, wd->inner); + assert(wd->inner->user_data == NULL); + + glDeleteTextures(1, &wd->inner->texture); + free(wd->inner); + free(wd); + gl_check_err(); +} + +void *gl_copy(backend_t *base, const void *image_data, const region_t *reg_visible) { + const struct gl_image *img = image_data; + auto new_img = ccalloc(1, struct gl_image); + *new_img = *img; + new_img->inner->refcount++; + return new_img; +} + +static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { + if (shader->prog) { + glDeleteProgram(shader->prog); + } + + shader->prog = 0; +} + +void gl_destroy_blur_context(backend_t *base, void *ctx) { + struct gl_blur_context *bctx = ctx; + // Free GLSL shaders/programs + for (int i = 0; i < bctx->npasses; ++i) { + gl_free_blur_shader(&bctx->blur_shader[i]); + } + free(bctx->blur_shader); + + glDeleteTextures(bctx->npasses > 1 ? 2 : 1, bctx->blur_texture); + if (bctx->npasses > 1) { + glDeleteFramebuffers(1, &bctx->blur_fbo); } - glEnd(); + free(bctx); + + gl_check_err(); } /** * Initialize GL blur filters. */ -static bool gl_init_blur(struct gl_data *gd, conv *const *const kernels) { - if (!kernels[0]) { - return true; +void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { + bool success = true; + auto gd = (struct gl_data *)base; + + struct conv **kernels; + auto ctx = ccalloc(1, struct gl_blur_context); + + if (!method || method >= BLUR_METHOD_INVALID) { + ctx->method = BLUR_METHOD_NONE; + return ctx; + } + + ctx->method = BLUR_METHOD_KERNEL; + if (method == BLUR_METHOD_KERNEL) { + ctx->npasses = ((struct kernel_blur_args *)args)->kernel_count; + kernels = ((struct kernel_blur_args *)args)->kernels; + } else { + kernels = generate_blur_kernel(method, args, &ctx->npasses); } + if (!ctx->npasses) { + ctx->method = BLUR_METHOD_NONE; + return ctx; + } + + ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t); + char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane // Thanks to hiciu for reporting. setlocale(LC_NUMERIC, "C"); // clang-format off - static const char *FRAG_SHADER_BLUR = GLSL(130, + static const char *FRAG_SHADER_BLUR = GLSL(330, %s\n // other extension pragmas - uniform float offset_x; - uniform float offset_y; uniform sampler2D tex_scr; uniform float opacity; + in vec2 texcoord; out vec4 out_color; void main() { vec4 sum = vec4(0.0, 0.0, 0.0, 0.0); @@ -510,21 +806,19 @@ static bool gl_init_blur(struct gl_data *gd, conv *const *const kernels) { ); static const char *FRAG_SHADER_BLUR_ADD = QUOTE( sum += float(%.7g) * - texture2D(tex_scr, vec2(gl_TexCoord[0].x + offset_x * float(%d), - gl_TexCoord[0].y + offset_y * float(%d))); + texelFetch(tex_scr, ivec2(texcoord + vec2(%d, %d)), 0); ); // clang-format on const char *shader_add = FRAG_SHADER_BLUR_ADD; char *extension = strdup(""); - gl_blur_shader_t *passes = gd->blur_shader; - for (int i = 0; i < MAX_BLUR_PASS && kernels[i]; gd->npasses = ++i) { + for (int i = 0; i < ctx->npasses; i++) { auto kern = kernels[i]; // Build shader int width = kern->w, height = kern->h; - int nele = width * height - 1; - size_t body_len = (strlen(shader_add) + 42) * nele; + int nele = width * height; + size_t body_len = (strlen(shader_add) + 42) * (uint)nele; char *shader_body = ccalloc(body_len, char); char *pc = shader_body; @@ -532,90 +826,97 @@ static bool gl_init_blur(struct gl_data *gd, conv *const *const kernels) { for (int j = 0; j < height; ++j) { for (int k = 0; k < width; ++k) { double val; - if (height / 2 == j && width / 2 == k) { - val = 1; - } else { - val = kern->data[j * width + k]; - } + val = kern->data[j * width + k]; if (val == 0) { continue; } sum += val; - pc += snprintf(pc, body_len - (pc - shader_body), + pc += snprintf(pc, body_len - (ulong)(pc - shader_body), FRAG_SHADER_BLUR_ADD, val, k - width / 2, j - height / 2); assert(pc < shader_body + body_len); } } - auto pass = passes + i; + auto pass = ctx->blur_shader + i; size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) + strlen(shader_body) + 10 /* sum */ + 1 /* null terminator */; char *shader_str = ccalloc(shader_len, char); - size_t real_shader_len = snprintf( - shader_str, shader_len, FRAG_SHADER_BLUR, extension, shader_body, sum); - assert(real_shader_len < shader_len); + auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR, + extension, shader_body, sum); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); free(shader_body); // Build program - pass->prog = gl_create_program_from_str(NULL, shader_str); + pass->prog = gl_create_program_from_str(vertex_shader, shader_str); free(shader_str); if (!pass->prog) { log_error("Failed to create GLSL program."); - goto err; + success = false; + goto out; } glBindFragDataLocation(pass->prog, 0, "out_color"); // Get uniform addresses - pass->unifm_offset_x = - glGetUniformLocationChecked(pass->prog, "offset_x"); - pass->unifm_offset_y = - glGetUniformLocationChecked(pass->prog, "offset_y"); pass->unifm_opacity = glGetUniformLocationChecked(pass->prog, "opacity"); + pass->orig_loc = glGetUniformLocationChecked(pass->prog, "orig"); + ctx->resize_width += kern->w / 2; + ctx->resize_height += kern->h / 2; } - free(extension); // Texture size will be defined by gl_resize - glGenTextures(gd->npasses > 1 ? 2 : 1, gd->blur_texture); - glBindTexture(GL_TEXTURE_2D, gd->blur_texture[0]); + glGenTextures(ctx->npasses > 1 ? 2 : 1, ctx->blur_texture); + glBindTexture(GL_TEXTURE_2D, ctx->blur_texture[0]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - if (gd->npasses > 1) { - glBindTexture(GL_TEXTURE_2D, gd->blur_texture[1]); + if (ctx->npasses > 1) { + glBindTexture(GL_TEXTURE_2D, ctx->blur_texture[1]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Generate FBO and textures when needed - glGenFramebuffers(1, &gd->blur_fbo); - if (!gd->blur_fbo) { + glGenFramebuffers(1, &ctx->blur_fbo); + if (!ctx->blur_fbo) { log_error("Failed to generate framebuffer object for blur"); - return false; + success = false; + goto out; } } - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); +out: + if (method != BLUR_METHOD_KERNEL) { + // We generated the blur kernels, so we need to free them + for (int i = 0; i < ctx->npasses; i++) { + free(kernels[i]); + } + free(kernels); + } - gl_check_err(); + if (!success) { + gl_destroy_blur_context(&gd->base, ctx); + ctx = NULL; + } - return true; -err: free(extension); + // Restore LC_NUMERIC setlocale(LC_NUMERIC, lc_numeric_old); free(lc_numeric_old); - return false; + + gl_check_err(); + return ctx; } // clang-format off -const char *win_shader_glsl = GLSL(110, +const char *win_shader_glsl = GLSL(330, uniform float opacity; uniform float dim; uniform bool invert_color; + in vec2 texcoord; uniform sampler2D tex; void main() { - vec4 c = texture2D(tex, gl_TexCoord[0].xy); + vec4 c = texelFetch(tex, ivec2(texcoord), 0); if (invert_color) { c = vec4(c.aaa - c.rgb, c.a); } @@ -627,10 +928,6 @@ const char *win_shader_glsl = GLSL(110, bool gl_init(struct gl_data *gd, session_t *ps) { // Initialize GLX data structure - for (int i = 0; i < MAX_BLUR_PASS; ++i) { - gd->blur_shader[i] = (gl_blur_shader_t){.prog = 0}; - } - glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); @@ -648,38 +945,37 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - gd->npasses = 0; - gl_win_shader_from_string(NULL, win_shader_glsl, &gd->win_shader); - if (!gl_init_blur(gd, ps->o.blur_kerns)) { - return false; - } + gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader); + gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag); + gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color"); // Set up the size of the viewport. We do this last because it expects the blur // textures are already set up. gl_resize(gd, ps->root_width, ps->root_height); - return true; -} + gd->logger = gl_string_marker_logger_new(); + if (gd->logger) { + log_add_target_tls(gd->logger); + } -static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { - if (shader->prog) { - glDeleteProgram(shader->prog); + const char *vendor = (const char *)glGetString(GL_VENDOR); + log_debug("GL_VENDOR = %s", vendor); + if (strcmp(vendor, "NVIDIA Corporation") == 0) { + log_info("GL vendor is NVIDIA, don't use glFinish"); + gd->is_nvidia = true; + } else { + gd->is_nvidia = false; } - shader->prog = 0; + return true; } void gl_deinit(struct gl_data *gd) { - // Free GLSL shaders/programs - for (int i = 0; i < MAX_BLUR_PASS; ++i) { - gl_free_blur_shader(&gd->blur_shader[i]); - } - gl_free_prog_main(&gd->win_shader); - glDeleteTextures(gd->npasses > 1 ? 2 : 1, gd->blur_texture); - if (gd->npasses > 1) { - glDeleteFramebuffers(1, &gd->blur_fbo); + if (gd->logger) { + log_remove_target_tls(gd->logger); + gd->logger = NULL; } gl_check_err(); @@ -693,7 +989,6 @@ GLuint gl_new_texture(GLenum target) { return 0; } - glEnable(target); glBindTexture(target, texture); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -704,10 +999,91 @@ GLuint gl_new_texture(GLenum target) { return texture; } +/// Decouple `img` from the image it references, also applies all the lazy operations +static inline void gl_image_decouple(backend_t *base, struct gl_image *img) { + if (img->inner->refcount == 1) { + return; + } + + struct gl_data *gl = (void *)base; + auto new_tex = cmalloc(struct gl_texture); + + glGenTextures(1, &new_tex->texture); + glBindTexture(GL_TEXTURE_2D, new_tex->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img->inner->width, img->inner->height, 0, + GL_BGRA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + new_tex->y_inverted = true; + new_tex->height = img->inner->height; + new_tex->width = img->inner->width; + new_tex->refcount = 1; + new_tex->user_data = gl->decouple_texture_user_data(base, img->inner->user_data); + + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + new_tex->texture, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + // clang-format off + GLint coord[] = { + // top left + 0, 0, // vertex coord + 0, 0, // texture coord + + // top right + img->inner->width, 0, // vertex coord + img->inner->width, 0, // texture coord + + // bottom right + img->inner->width, img->inner->height, + img->inner->width, img->inner->height, + + // bottom left + 0, img->inner->height, + 0, img->inner->height, + }; + // clang-format on + + _gl_compose(base, img, fbo, coord, (GLuint[]){0, 1, 2, 2, 3, 0}, 1); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + + img->inner->refcount--; + img->inner = new_tex; + + // Clear lazy operation flags + img->color_inverted = false; + img->dim = 0; + img->opacity = 1; +} + +static void gl_image_apply_alpha(backend_t *base, struct gl_image *img, + const region_t *reg_op, double alpha) { + glBlendFunc(GL_ONE, GL_CONSTANT_COLOR); + glBlendColor((GLclampf)alpha, (GLclampf)alpha, (GLclampf)alpha, (GLclampf)alpha); + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + img->inner->texture, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, 0, false); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); +} + /// stub for backend_operations::image_op bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, const region_t *reg_op, const region_t *reg_visible, void *arg) { - struct gl_texture *tex = image_data; + struct gl_image *tex = image_data; int *iargs = arg; switch (op) { case IMAGE_OP_INVERT_COLOR_ALL: tex->color_inverted = true; break; @@ -716,8 +1092,9 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, break; case IMAGE_OP_APPLY_ALPHA_ALL: tex->opacity *= *(double *)arg; break; case IMAGE_OP_APPLY_ALPHA: - // TODO - log_warn("IMAGE_OP_APPLY_ALPHA not implemented yet"); + gl_image_decouple(base, tex); + assert(tex->inner->refcount == 1); + gl_image_apply_alpha(base, tex, reg_op, *(double *)arg); break; case IMAGE_OP_RESIZE_TILE: // texture is already set to repeat, so nothing else we need to do @@ -730,6 +1107,6 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, } bool gl_is_image_transparent(backend_t *base, void *image_data) { - gl_texture_t *img = image_data; + struct gl_image *img = image_data; return img->has_alpha; } diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index cc215a0075..1bafea2a9f 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -26,53 +26,51 @@ typedef struct { // Program and uniforms for blur shader typedef struct { GLuint prog; - GLint unifm_offset_x; - GLint unifm_offset_y; GLint unifm_opacity; + GLint orig_loc; } gl_blur_shader_t; +typedef struct { + GLuint prog; + GLint color_loc; +} gl_fill_shader_t; + +struct gl_texture { + int refcount; + GLuint texture; + int width, height; + bool y_inverted; + void *user_data; +}; + /// @brief Wrapper of a binded GLX texture. -typedef struct gl_texture { +typedef struct gl_image { + struct gl_texture *inner; double opacity; double dim; - int *refcount; - GLuint texture; - // The size of the backing texture - int width, height; - // The effective size of the texture int ewidth, eheight; - unsigned depth; - bool y_inverted; bool has_alpha; bool color_inverted; -} gl_texture_t; +} gl_image_t; struct gl_data { backend_t base; + // If we are using proprietary NVIDIA driver + bool is_nvidia; // Height and width of the viewport int height, width; - int npasses; gl_win_shader_t win_shader; - gl_blur_shader_t blur_shader[MAX_BLUR_PASS]; - - // Temporary textures used for blurring. They are always the same size as the - // target, so they are always big enough without resizing. - // Turns out calling glTexImage to resize is expensive, so we avoid that. - GLuint blur_texture[2]; - // Temporary fbo used for blurring - GLuint blur_fbo; -}; + gl_fill_shader_t fill_shader; -typedef struct { - /// Framebuffer used for blurring. - GLuint fbo; - /// Textures used for blurring. - GLuint textures[2]; - /// Width of the textures. - int width; - /// Height of the textures. - int height; -} gl_blur_cache_t; + /// Called when an gl_texture is decoupled from the texture it refers. Returns + /// the decoupled user_data + void *(*decouple_texture_user_data)(backend_t *base, void *user_data); + + /// Release the user data attached to a gl_texture + void (*release_user_data)(backend_t *base, struct gl_texture *); + + struct log_target *logger; +}; typedef struct session session_t; @@ -99,11 +97,17 @@ GLuint gl_new_texture(GLenum target); bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, const region_t *reg_op, const region_t *reg_visible, void *arg); -bool gl_blur(backend_t *base, double opacity, const region_t *reg_blur, +void gl_release_image(backend_t *base, void *image_data); + +void *gl_copy(backend_t *base, const void *image_data, const region_t *reg_visible); + +bool gl_blur(backend_t *base, double opacity, void *, const region_t *reg_blur, const region_t *reg_visible); +void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); +void gl_destroy_blur_context(backend_t *base, void *ctx); bool gl_is_image_transparent(backend_t *base, void *image_data); -void gl_fill(backend_t *base, double r, double g, double b, double a, const region_t *clip); +void gl_fill(backend_t *base, struct color, const region_t *clip); static inline void gl_delete_texture(GLuint texture) { glDeleteTextures(1, &texture); @@ -146,24 +150,28 @@ static inline void gl_check_err_(const char *func, int line) { } } +static inline void gl_clear_err(void) { + while (glGetError() != GL_NO_ERROR); +} + #define gl_check_err() gl_check_err_(__func__, __LINE__) /** * Check if a GLX extension exists. */ static inline bool gl_has_extension(const char *ext) { - GLint nexts = 0; + int nexts = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &nexts); - if (!nexts) { - log_error("Failed to get GL extension list."); - return false; - } - - for (int i = 0; i < nexts; i++) { - const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, i); - if (strcmp(ext, exti) == 0) + for (int i = 0; i < nexts || !nexts; i++) { + const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i); + if (exti == NULL) { + break; + } + if (strcmp(ext, exti) == 0) { return true; + } } + gl_clear_err(); log_info("Missing GL extension %s.", ext); return false; } diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index ff65fe6c39..036243feec 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -25,6 +25,7 @@ #include "backend/gl/gl_common.h" #include "backend/gl/glx.h" #include "common.h" +#include "compton.h" #include "compiler.h" #include "config.h" #include "log.h" @@ -33,8 +34,7 @@ #include "win.h" #include "x.h" -struct _glx_image_data { - gl_texture_t texture; +struct _glx_pixmap { GLXPixmap glpixmap; xcb_pixmap_t pixmap; bool owned; @@ -44,7 +44,7 @@ struct _glx_data { struct gl_data gl; Display *display; int screen; - int target_win; + xcb_window_t target_win; int glx_event; int glx_error; GLXContext ctx; @@ -70,7 +70,7 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, true, - GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, GLX_DONT_CARE, + GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, (GLint)GLX_DONT_CARE, GLX_BUFFER_SIZE, m.red_size + m.green_size + m.blue_size + m.alpha_size, GLX_RED_SIZE, m.red_size, @@ -117,7 +117,8 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi int visual; glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_VISUAL_ID, &visual); if (m.visual_depth != -1 && - x_get_visual_depth(XGetXCBConnection(dpy), visual) != m.visual_depth) { + x_get_visual_depth(XGetXCBConnection(dpy), (xcb_visualid_t)visual) != + m.visual_depth) { // Some driver might attach fbconfig to a GLX visual with a // different depth. // @@ -159,38 +160,30 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi /** * Free a glx_texture_t. */ -void glx_release_image(backend_t *base, void *image_data) { - struct _glx_image_data *wd = image_data; +static void glx_release_image(backend_t *base, struct gl_texture *tex) { struct _glx_data *gd = (void *)base; - (*wd->texture.refcount)--; - if (*wd->texture.refcount != 0) { - free(wd); - return; - } + + struct _glx_pixmap *p = tex->user_data; // Release binding - if (wd->glpixmap && wd->texture.texture) { - glBindTexture(GL_TEXTURE_2D, wd->texture.texture); - glXReleaseTexImageEXT(gd->display, wd->glpixmap, GLX_FRONT_LEFT_EXT); + if (p->glpixmap && tex->texture) { + glBindTexture(GL_TEXTURE_2D, tex->texture); + glXReleaseTexImageEXT(gd->display, p->glpixmap, GLX_FRONT_LEFT_EXT); glBindTexture(GL_TEXTURE_2D, 0); } // Free GLX Pixmap - if (wd->glpixmap) { - glXDestroyPixmap(gd->display, wd->glpixmap); - wd->glpixmap = 0; + if (p->glpixmap) { + glXDestroyPixmap(gd->display, p->glpixmap); + p->glpixmap = 0; } - if (wd->owned) { - xcb_free_pixmap(base->c, wd->pixmap); - wd->pixmap = XCB_NONE; + if (p->owned) { + xcb_free_pixmap(base->c, p->pixmap); + p->pixmap = XCB_NONE; } - glDeleteTextures(1, &wd->texture.texture); - free(wd->texture.refcount); - - // Free structure itself - free(wd); - gl_check_err(); + free(p); + tex->user_data = NULL; } /** @@ -210,6 +203,14 @@ void glx_deinit(backend_t *base) { free(gd); } +static void *glx_decouple_user_data(backend_t * attr_unused base, void * attr_unused ud) { + auto ret = cmalloc(struct _glx_pixmap); + ret->owned = false; + ret->glpixmap = 0; + ret->pixmap = 0; + return ret; +} + /** * Initialize OpenGL. */ @@ -217,11 +218,11 @@ static backend_t *glx_init(session_t *ps) { bool success = false; glxext_init(ps->dpy, ps->scr); auto gd = ccalloc(1, struct _glx_data); - gd->gl.base.c = ps->c; - gd->gl.base.root = ps->root; + init_backend_base(&gd->gl.base, ps); + gd->display = ps->dpy; gd->screen = ps->scr; - gd->target_win = ps->overlay != XCB_NONE ? ps->overlay : ps->root; + gd->target_win = session_get_target_window(ps); XVisualInfo *pvis = NULL; @@ -289,7 +290,9 @@ static backend_t *glx_init(session_t *ps) { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, - 0, + 3, + GLX_CONTEXT_PROFILE_MASK_ARB, + GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0, }); free(cfg); @@ -308,10 +311,7 @@ static backend_t *glx_init(session_t *ps) { } // Attach GLX context - GLXDrawable tgt = ps->overlay; - if (!tgt) { - tgt = ps->root; - } + GLXDrawable tgt = gd->target_win; if (!glXMakeCurrent(ps->dpy, tgt, gd->ctx)) { log_error("Failed to attach GLX context."); goto end; @@ -322,6 +322,9 @@ static backend_t *glx_init(session_t *ps) { goto end; } + gd->gl.decouple_texture_user_data = glx_decouple_user_data; + gd->gl.release_user_data = glx_release_image; + success = true; end: @@ -340,6 +343,7 @@ static backend_t *glx_init(session_t *ps) { static void * glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { struct _glx_data *gd = (void *)base; + struct _glx_pixmap *glxpixmap = NULL; // Retrieve pixmap parameters, if they aren't provided if (fmt.visual_depth > OPENGL_MAX_DEPTH) { log_error("Requested depth %d higher than max possible depth %d.", @@ -347,16 +351,22 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b return false; } + if (fmt.visual_depth < 0) { + log_error("Pixmap %#010x with invalid depth %d", pixmap, fmt.visual_depth); + return false; + } + auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL); if (!r) { log_error("Invalid pixmap %#010x", pixmap); return NULL; } - auto wd = ccalloc(1, struct _glx_image_data); - wd->pixmap = pixmap; - wd->texture.width = wd->texture.ewidth = r->width; - wd->texture.height = wd->texture.eheight = r->height; + log_trace("Binding pixmap %#010x", pixmap); + auto wd = ccalloc(1, struct gl_image); + wd->inner = ccalloc(1, struct gl_texture); + wd->inner->width = wd->ewidth = r->width; + wd->inner->height = wd->eheight = r->height; free(r); auto fbcfg = glx_find_fbconfig(gd->display, gd->screen, fmt); @@ -374,7 +384,7 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b } log_debug("depth %d, rgba %d", fmt.visual_depth, - (fbcfg->texture_fmt = GLX_TEXTURE_FORMAT_RGBA_EXT)); + (fbcfg->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT)); GLint attrs[] = { GLX_TEXTURE_FORMAT_EXT, @@ -384,36 +394,41 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b 0, }; - wd->texture.y_inverted = fbcfg->y_inverted; + wd->inner->y_inverted = fbcfg->y_inverted; - wd->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, wd->pixmap, attrs); + glxpixmap = cmalloc(struct _glx_pixmap); + glxpixmap->pixmap = pixmap; + glxpixmap->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, pixmap, attrs); + glxpixmap->owned = owned; free(fbcfg); - if (!wd->glpixmap) { + if (!glxpixmap->glpixmap) { log_error("Failed to create glpixmap for pixmap %#010x", pixmap); goto err; } + log_trace("GLXPixmap %#010lx", glxpixmap->glpixmap); + // Create texture - wd->texture.texture = gl_new_texture(GL_TEXTURE_2D); - wd->texture.opacity = 1; - wd->texture.depth = fmt.visual_depth; - wd->texture.color_inverted = false; - wd->texture.dim = 0; - wd->texture.has_alpha = fmt.alpha_size != 0; - wd->texture.refcount = ccalloc(1, int); - *wd->texture.refcount = 1; - wd->owned = owned; - glBindTexture(GL_TEXTURE_2D, wd->texture.texture); - glXBindTexImageEXT(gd->display, wd->glpixmap, GLX_FRONT_LEFT_EXT, NULL); + wd->inner->user_data = glxpixmap; + wd->inner->texture = gl_new_texture(GL_TEXTURE_2D); + wd->opacity = 1; + wd->color_inverted = false; + wd->dim = 0; + wd->has_alpha = fmt.alpha_size != 0; + wd->inner->refcount = 1; + glBindTexture(GL_TEXTURE_2D, wd->inner->texture); + glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL); glBindTexture(GL_TEXTURE_2D, 0); gl_check_err(); return wd; err: - if (wd->glpixmap) { - glXDestroyPixmap(gd->display, wd->glpixmap); + if (glxpixmap && glxpixmap->glpixmap) { + glXDestroyPixmap(gd->display, glxpixmap->glpixmap); } + free(glxpixmap); + if (owned) { xcb_free_pixmap(base->c, pixmap); } @@ -424,6 +439,10 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b static void glx_present(backend_t *base) { struct _glx_data *gd = (void *)base; glXSwapBuffers(gd->display, gd->target_win); + // XXX there should be no need to block compton will wait for render to finish + if (!gd->gl.is_nvidia) { + glFinish(); + } } static int glx_buffer_age(backend_t *base) { @@ -437,28 +456,22 @@ static int glx_buffer_age(backend_t *base) { return (int)val ?: -1; } -static void *glx_copy(backend_t *base, const void *image_data, const region_t *reg_visible) { - const struct _glx_image_data *img = image_data; - auto new_img = ccalloc(1, struct _glx_image_data); - *new_img = *img; - (*new_img->texture.refcount)++; - return new_img; -} - struct backend_operations glx_ops = { .init = glx_init, .deinit = glx_deinit, .bind_pixmap = glx_bind_pixmap, - .release_image = glx_release_image, + .release_image = gl_release_image, .compose = gl_compose, .image_op = gl_image_op, - .copy = glx_copy, + .copy = gl_copy, .blur = gl_blur, .is_image_transparent = gl_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, .render_shadow = default_backend_render_shadow, .fill = gl_fill, + .create_blur_context = gl_create_blur_context, + .destroy_blur_context = gl_destroy_blur_context, .max_buffer_age = 5, // Why? }; @@ -472,7 +485,7 @@ static inline bool glx_has_extension(Display *dpy, int screen, const char *ext) return false; } - long inlen = strlen(ext); + auto inlen = strlen(ext); const char *curr = glx_exts; bool match = false; while (curr && !match) { @@ -480,9 +493,9 @@ static inline bool glx_has_extension(Display *dpy, int screen, const char *ext) if (!end) { // Last extension string match = strcmp(ext, curr) == 0; - } else if (end - curr == inlen) { + } else if (curr + inlen == end) { // Length match, do match string - match = strncmp(ext, curr, end - curr) == 0; + match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0; } curr = end ? end + 1 : NULL; } diff --git a/src/backend/meson.build b/src/backend/meson.build index 6cd8e94e4f..9318b78f5d 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -1,5 +1,5 @@ # enable xrender -srcs += [ files('backend_common.c', 'xrender.c', 'backend.c') ] +srcs += [ files('backend_common.c', 'xrender/xrender.c', 'backend.c', 'driver.c') ] # enable opengl if get_option('opengl') diff --git a/src/backend/xrender.c b/src/backend/xrender/xrender.c similarity index 67% rename from src/backend/xrender.c rename to src/backend/xrender/xrender.c index 4b67aec6be..829e20c77c 100644 --- a/src/backend/xrender.c +++ b/src/backend/xrender/xrender.c @@ -14,6 +14,7 @@ #include "backend/backend.h" #include "backend/backend_common.h" #include "common.h" +#include "compton.h" #include "config.h" #include "kernel.h" #include "log.h" @@ -22,8 +23,6 @@ #include "win.h" #include "x.h" -#define auto __auto_type - typedef struct _xrender_data { backend_t base; /// If vsync is enabled and supported by the current system @@ -61,23 +60,29 @@ typedef struct _xrender_data { /// Width and height of the target pixmap int target_width, target_height; - /// Blur kernels converted to X format - xcb_render_fixed_t *x_blur_kern[MAX_BLUR_PASS]; - /// Number of elements in each blur kernel - size_t x_blur_kern_size[MAX_BLUR_PASS]; - xcb_special_event_t *present_event; } xrender_data; +struct _xrender_blur_context { + enum blur_method method; + /// Blur kernels converted to X format + struct x_convolution_kernel **x_blur_kernel; + + int resize_width, resize_height; + + /// Number of blur kernels + int x_blur_kernel_count; +}; + struct _xrender_image_data { // Pixmap that the client window draws to, // it will contain the content of client window. xcb_pixmap_t pixmap; // A Picture links to the Pixmap xcb_render_picture_t pict; - long width, height; + int width, height; // The effective size of the image - long ewidth, eheight; + int ewidth, eheight; bool has_alpha; double opacity; xcb_visualid_t visual; @@ -89,8 +94,8 @@ static void compose(backend_t *base, void *img_data, int dst_x, int dst_y, const region_t *reg_paint, const region_t *reg_visible) { struct _xrender_data *xd = (void *)base; struct _xrender_image_data *img = img_data; - int op = (img->has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC); - auto alpha_pict = xd->alpha_pict[(int)(img->opacity * 255.0)]; + uint8_t op = (img->has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC); + auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; region_t reg; pixman_region32_init(®); pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible); @@ -101,29 +106,36 @@ static void compose(backend_t *base, void *img_data, int dst_x, int dst_y, x_set_picture_clip_region(base->c, xd->back[xd->curr_back], 0, 0, ®); xcb_render_composite(base->c, op, img->pict, alpha_pict, xd->back[xd->curr_back], - 0, 0, 0, 0, dst_x, dst_y, img->ewidth, img->eheight); + 0, 0, 0, 0, to_i16_checked(dst_x), to_i16_checked(dst_y), + to_u16_checked(img->ewidth), to_u16_checked(img->eheight)); pixman_region32_fini(®); } -static void -fill(backend_t *base, double r, double g, double b, double a, const region_t *clip) { +static void fill(backend_t *base, struct color c, const region_t *clip) { struct _xrender_data *xd = (void *)base; const rect_t *extent = pixman_region32_extents((region_t *)clip); x_set_picture_clip_region(base->c, xd->back[xd->curr_back], 0, 0, clip); // color is in X fixed point representation xcb_render_fill_rectangles( base->c, XCB_RENDER_PICT_OP_OVER, xd->back[xd->curr_back], - (xcb_render_color_t){ - .red = r * 0xffff, .green = g * 0xffff, .blue = b * 0xffff, .alpha = a * 0xffff}, + (xcb_render_color_t){.red = (uint16_t)(c.red * 0xffff), + .green = (uint16_t)(c.green * 0xffff), + .blue = (uint16_t)(c.blue * 0xffff), + .alpha = (uint16_t)(c.alpha * 0xffff)}, 1, - (xcb_rectangle_t[]){{.x = extent->x1, - .y = extent->y1, - .width = extent->x2 - extent->x1, - .height = extent->y2 - extent->y1}}); + (xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1), + .y = to_i16_checked(extent->y1), + .width = to_u16_checked(extent->x2 - extent->x1), + .height = to_u16_checked(extent->y2 - extent->y1)}}); } -static bool blur(backend_t *backend_data, double opacity, const region_t *reg_blur, - const region_t *reg_visible) { +static bool blur(backend_t *backend_data, double opacity, void *ctx_, + const region_t *reg_blur, const region_t *reg_visible) { + struct _xrender_blur_context *bctx = ctx_; + if (bctx->method == BLUR_METHOD_NONE) { + return true; + } + struct _xrender_data *xd = (void *)backend_data; xcb_connection_t *c = xd->base.c; region_t reg_op; @@ -134,20 +146,22 @@ static bool blur(backend_t *backend_data, double opacity, const region_t *reg_bl return true; } - const pixman_box32_t *extent = pixman_region32_extents(®_op); - const int height = extent->y2 - extent->y1; - const int width = extent->x2 - extent->x1; - int src_x = extent->x1, src_y = extent->y1; + region_t reg_op_resized = + resize_region(®_op, bctx->resize_width, bctx->resize_height); + + const pixman_box32_t *extent_resized = pixman_region32_extents(®_op_resized); + const auto height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1); + const auto width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1); static const char *filter0 = "Nearest"; // The "null" filter static const char *filter = "convolution"; // Create a buffer for storing blurred picture, make it just big enough // for the blur region xcb_render_picture_t tmp_picture[2] = { - x_create_picture_with_visual(xd->base.c, xd->base.root, width, height, - xd->default_visual, 0, NULL), - x_create_picture_with_visual(xd->base.c, xd->base.root, width, height, - xd->default_visual, 0, NULL)}; + x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, + height_resized, xd->default_visual, 0, NULL), + x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, + height_resized, xd->default_visual, 0, NULL)}; if (!tmp_picture[0] || !tmp_picture[1]) { log_error("Failed to build intermediate Picture."); @@ -157,18 +171,16 @@ static bool blur(backend_t *backend_data, double opacity, const region_t *reg_bl region_t clip; pixman_region32_init(&clip); - pixman_region32_copy(&clip, ®_op); - pixman_region32_translate(&clip, -src_x, -src_y); + pixman_region32_copy(&clip, ®_op_resized); + pixman_region32_translate(&clip, -extent_resized->x1, -extent_resized->y1); x_set_picture_clip_region(c, tmp_picture[0], 0, 0, &clip); x_set_picture_clip_region(c, tmp_picture[1], 0, 0, &clip); pixman_region32_fini(&clip); - // The multipass blur implemented here is not correct, but this is what old - // compton did anyway. XXX xcb_render_picture_t src_pict = xd->back[xd->curr_back], dst_pict = tmp_picture[0]; - auto alpha_pict = xd->alpha_pict[(int)(opacity * 255)]; + auto alpha_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)]; int current = 0; - x_set_picture_clip_region(c, src_pict, 0, 0, ®_op); + x_set_picture_clip_region(c, src_pict, 0, 0, ®_op_resized); // For more than 1 pass, we do: // back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ... @@ -176,43 +188,55 @@ static bool blur(backend_t *backend_data, double opacity, const region_t *reg_bl // For 1 pass, we do // back -(pass 1)-> tmp0 -(copy)-> target_buffer int i; - for (i = 0; xd->x_blur_kern[i]; i++) { - assert(i < MAX_BLUR_PASS - 1); - + for (i = 0; i < bctx->x_blur_kernel_count; i++) { // Copy from source picture to destination. The filter must // be applied on source picture, to get the nearby pixels outside the // window. - // TODO cache converted blur_kerns - xcb_render_set_picture_filter(c, src_pict, strlen(filter), filter, - xd->x_blur_kern_size[i], xd->x_blur_kern[i]); - - if (xd->x_blur_kern[i + 1] || i == 0) { - // This is not the last pass, or this is the first pass + xcb_render_set_picture_filter(c, src_pict, to_u16_checked(strlen(filter)), + filter, + to_u32_checked(bctx->x_blur_kernel[i]->size), + bctx->x_blur_kernel[i]->kernel); + + if (i == 0) { + // First pass, back buffer -> tmp picture + // (we do this even if this is also the last pass, because we + // cannot do back buffer -> back buffer) + xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, + dst_pict, to_i16_checked(extent_resized->x1), + to_i16_checked(extent_resized->y1), 0, 0, 0, + 0, width_resized, height_resized); + } else if (i < bctx->x_blur_kernel_count - 1) { + // This is not the last pass or the first pass, + // tmp picture 1 -> tmp picture 2 xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, - XCB_NONE, dst_pict, src_x, src_y, 0, 0, 0, 0, - width, height); + XCB_NONE, dst_pict, 0, 0, 0, 0, 0, 0, + width_resized, height_resized); } else { - // This is the last pass, and this is also not the first + x_set_picture_clip_region(c, xd->back[xd->curr_back], 0, 0, ®_op); + // This is the last pass, and we are doing more than 1 pass xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[xd->curr_back], 0, 0, 0, - 0, src_x, src_y, width, height); + 0, to_i16_checked(extent_resized->x1), + to_i16_checked(extent_resized->y1), + width_resized, height_resized); } // reset filter - xcb_render_set_picture_filter(c, src_pict, strlen(filter0), filter0, 0, NULL); + xcb_render_set_picture_filter( + c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL); src_pict = tmp_picture[current]; dst_pict = tmp_picture[!current]; - src_x = 0; - src_y = 0; current = !current; } // There is only 1 pass if (i == 1) { - xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, - xd->back[xd->curr_back], 0, 0, 0, 0, extent->x1, - extent->y1, width, height); + x_set_picture_clip_region(c, xd->back[xd->curr_back], 0, 0, ®_op); + xcb_render_composite( + c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, + xd->back[xd->curr_back], 0, 0, 0, 0, to_i16_checked(extent_resized->x1), + to_i16_checked(extent_resized->y1), width_resized, height_resized); } xcb_render_free_picture(c, tmp_picture[0]); @@ -232,7 +256,7 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool } auto img = ccalloc(1, struct _xrender_image_data); - img->depth = fmt.visual_depth; + img->depth = (uint8_t)fmt.visual_depth; img->width = img->ewidth = r->width; img->height = img->eheight = r->height; img->pixmap = pixmap; @@ -255,6 +279,7 @@ static void release_image(backend_t *base, void *image) { if (img->owned) { xcb_free_pixmap(base->c, img->pixmap); } + free(img); } static void deinit(backend_t *backend_data) { @@ -268,9 +293,6 @@ static void deinit(backend_t *backend_data) { xcb_render_free_picture(xd->base.c, xd->back[i]); xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]); } - for (int i = 0; xd->x_blur_kern[i]; i++) { - free(xd->x_blur_kern[i]); - } if (xd->present_event) { xcb_unregister_for_special_event(xd->base.c, xd->present_event); } @@ -328,7 +350,8 @@ static void present(backend_t *base) { // but that will require a different backend API xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[xd->curr_back], XCB_NONE, xd->target, 0, 0, - 0, 0, 0, 0, xd->target_width, xd->target_height); + 0, 0, 0, 0, to_u16_checked(xd->target_width), + to_u16_checked(xd->target_height)); xd->buffer_age[xd->curr_back] = 1; } } @@ -358,6 +381,8 @@ static bool image_op(backend_t *base, enum image_operations op, void *image, pixman_region32_init(®); + const auto tmpw = to_u16_checked(img->width); + const auto tmph = to_u16_checked(img->height); switch (op) { case IMAGE_OP_INVERT_COLOR_ALL: x_set_picture_clip_region(base->c, img->pict, 0, 0, reg_visible); @@ -366,36 +391,33 @@ static bool image_op(backend_t *base, enum image_operations op, void *image, x_create_picture_with_visual(base->c, base->root, img->width, img->height, img->visual, 0, NULL); xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, img->pict, - XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, - img->width, img->height); + XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); xcb_render_composite(base->c, XCB_RENDER_PICT_OP_DIFFERENCE, - xd->white_pixel, XCB_NONE, tmp_pict, 0, 0, 0, - 0, 0, 0, img->width, img->height); - // We use an extra PictOpInReverse operation to get correct pixel - // alpha. There could be a better solution. + xd->white_pixel, XCB_NONE, img->pict, 0, 0, + 0, 0, 0, 0, tmpw, tmph); xcb_render_composite(base->c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict, XCB_NONE, img->pict, 0, 0, 0, 0, 0, - 0, img->width, img->height); + 0, tmpw, tmph); xcb_render_free_picture(base->c, tmp_pict); } else { xcb_render_composite(base->c, XCB_RENDER_PICT_OP_DIFFERENCE, xd->white_pixel, XCB_NONE, img->pict, 0, 0, - 0, 0, 0, 0, img->width, img->height); + 0, 0, 0, 0, tmpw, tmph); } break; case IMAGE_OP_DIM_ALL: x_set_picture_clip_region(base->c, img->pict, 0, 0, reg_visible); xcb_render_color_t color = { - .red = 0, .green = 0, .blue = 0, .alpha = 0xffff * dargs[0]}; + .red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * dargs[0])}; // Dim the actually content of window xcb_rectangle_t rect = { .x = 0, .y = 0, - .width = img->width, - .height = img->height, + .width = tmpw, + .height = tmph, }; xcb_render_fill_rectangles(base->c, XCB_RENDER_PICT_OP_OVER, img->pict, @@ -412,10 +434,10 @@ static bool image_op(backend_t *base, enum image_operations op, void *image, break; } - auto alpha_pict = xd->alpha_pict[(int)(dargs[0] * 255)]; + auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)]; x_set_picture_clip_region(base->c, img->pict, 0, 0, ®); - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_IN, img->pict, XCB_NONE, - alpha_pict, 0, 0, 0, 0, 0, 0, img->width, img->height); + xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OUT_REVERSE, alpha_pict, + XCB_NONE, img->pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); img->has_alpha = true; break; case IMAGE_OP_RESIZE_TILE: @@ -454,21 +476,66 @@ static void *copy(backend_t *base, const void *image, const region_t *reg) { return NULL; } - auto alpha_pict = - img->opacity == 1 ? XCB_NONE : xd->alpha_pict[(int)(img->opacity * 255)]; + xcb_render_picture_t alpha_pict = + img->opacity == 1 ? XCB_NONE : xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, img->pict, alpha_pict, - new_img->pict, 0, 0, 0, 0, 0, 0, img->width, img->height); + new_img->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(img->width), + to_u16_checked(img->height)); return new_img; } +void *create_blur_context(backend_t *base, enum blur_method method, void *args) { + auto ret = ccalloc(1, struct _xrender_blur_context); + if (!method || method >= BLUR_METHOD_INVALID) { + ret->method = BLUR_METHOD_NONE; + return ret; + } + + ret->method = BLUR_METHOD_KERNEL; + struct conv **kernels; + int kernel_count; + if (method == BLUR_METHOD_KERNEL) { + kernels = ((struct kernel_blur_args *)args)->kernels; + kernel_count = ((struct kernel_blur_args *)args)->kernel_count; + } else { + kernels = generate_blur_kernel(method, args, &kernel_count); + } + + ret->x_blur_kernel = ccalloc(kernel_count, struct x_convolution_kernel *); + for (int i = 0; i < kernel_count; i++) { + int center = kernels[i]->h * kernels[i]->w / 2; + x_create_convolution_kernel(kernels[i], kernels[i]->data[center], + &ret->x_blur_kernel[i]); + ret->resize_width += kernels[i]->w / 2; + ret->resize_height += kernels[i]->h / 2; + } + ret->x_blur_kernel_count = kernel_count; + + if (method != BLUR_METHOD_KERNEL) { + // Kernels generated by generate_blur_kernel, so we need to free them. + for (int i = 0; i < kernel_count; i++) { + free(kernels[i]); + } + free(kernels); + } + return ret; +} + +void destroy_blur_context(backend_t *base, void *ctx_) { + struct _xrender_blur_context *ctx = ctx_; + for (int i = 0; i < ctx->x_blur_kernel_count; i++) { + free(ctx->x_blur_kernel[i]); + } + free(ctx->x_blur_kernel); + free(ctx); +} + backend_t *backend_xrender_init(session_t *ps) { auto xd = ccalloc(1, struct _xrender_data); + init_backend_base(&xd->base, ps); - xd->base.c = ps->c; - xd->base.root = ps->root; - - for (int i = 0; i < 256; ++i) { - double o = (double)i / 255.0; + for (int i = 0; i <= MAX_ALPHA; ++i) { + double o = (double)i / (double)MAX_ALPHA; xd->alpha_pict[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0); assert(xd->alpha_pict[i] != XCB_NONE); } @@ -479,18 +546,12 @@ backend_t *backend_xrender_init(session_t *ps) { xd->black_pixel = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0); xd->white_pixel = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1); - if (ps->overlay != XCB_NONE) { - xd->target = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, ps->overlay, 0, NULL); - xd->target_win = ps->overlay; - } else { - xcb_render_create_picture_value_list_t pa = { - .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, - }; - xd->target = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - xd->target_win = ps->root; - } + xd->target_win = session_get_target_window(ps); + xcb_render_create_picture_value_list_t pa = { + .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, + }; + xd->target = x_create_picture_with_visual_and_pixmap( + ps->c, ps->vis, xd->target_win, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis); if (!pictfmt) { @@ -500,7 +561,7 @@ backend_t *backend_xrender_init(session_t *ps) { xd->vsync = ps->o.vsync; if (ps->present_exists) { - auto eid = xcb_generate_id(ps->c); + auto eid = x_new_id(ps->c); auto e = xcb_request_check(ps->c, xcb_present_select_input_checked( ps->c, eid, xd->target_win, @@ -526,7 +587,8 @@ backend_t *backend_xrender_init(session_t *ps) { int pixmap_needed = xd->vsync ? 2 : 1; for (int i = 0; i < pixmap_needed; i++) { xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root, - ps->root_width, ps->root_height); + to_u16_checked(ps->root_width), + to_u16_checked(ps->root_height)); xd->back[i] = x_create_picture_with_pictfmt_and_pixmap( ps->c, pictfmt, xd->back_pixmap[i], 0, NULL); xd->buffer_age[i] = -1; @@ -544,11 +606,6 @@ backend_t *backend_xrender_init(session_t *ps) { xd->root_pict = x_create_picture_with_visual_and_pixmap( ps->c, ps->vis, root_pixmap, 0, NULL); } - for (int i = 0; ps->o.blur_kerns[i]; i++) { - assert(i < MAX_BLUR_PASS - 1); - xd->x_blur_kern_size[i] = x_picture_filter_from_conv( - ps->o.blur_kerns[i], 1, &xd->x_blur_kern[i], (size_t[]){0}); - } return &xd->base; err: deinit(&xd->base); @@ -573,6 +630,8 @@ struct backend_operations xrender_ops = { .image_op = image_op, .copy = copy, + .create_blur_context = create_blur_context, + .destroy_blur_context = destroy_blur_context, }; // vim: set noet sw=8 ts=8: diff --git a/src/c2.c b/src/c2.c index fae0d26d2e..f2db608e5b 100644 --- a/src/c2.c +++ b/src/c2.c @@ -40,6 +40,7 @@ #include "utils.h" #include "win.h" #include "x.h" +#include "atom.h" #include "c2.h" @@ -106,8 +107,8 @@ struct _c2_l { xcb_atom_t tgtatom; bool tgt_onframe; int index; - enum { C2_L_PUNDEFINED, - C2_L_PID, + enum { C2_L_PUNDEFINED = -1, + C2_L_PID = 0, C2_L_PX, C2_L_PY, C2_L_PX2, @@ -210,6 +211,26 @@ static const c2_predef_t C2_PREDEFS[] = { [C2_L_PROLE] = {"role", C2_L_TSTRING, 0}, }; +/** + * Get the numeric property value from a win_prop_t. + */ +static inline long winprop_get_int(winprop_t prop) { + long tgt = 0; + + if (!prop.nitems) { + return 0; + } + + switch (prop.format) { + case 8: tgt = *(prop.p8); break; + case 16: tgt = *(prop.p16); break; + case 32: tgt = *(prop.p32); break; + default: assert(0); break; + } + + return tgt; +} + /** * Compare next word in a string with another string. */ @@ -310,7 +331,7 @@ static void attr_unused c2_dump(c2_ptr_t p); static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf); -static bool c2_match_once(session_t *ps, const win *w, const c2_ptr_t cond); +static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond); /** * Parse a condition string. @@ -554,17 +575,19 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { } // Copy target name out - unsigned tgtlen = 0; + int tgtlen = 0; for (; pattern[offset] && (isalnum(pattern[offset]) || '_' == pattern[offset]); ++offset) { ++tgtlen; } - if (!tgtlen) + if (!tgtlen) { c2_error("Empty target."); - pleaf->tgt = strndup(&pattern[offset - tgtlen], tgtlen); + } + pleaf->tgt = strndup(&pattern[offset - tgtlen], (size_t)tgtlen); // Check for predefined targets - for (unsigned i = 1; i < sizeof(C2_PREDEFS) / sizeof(C2_PREDEFS[0]); ++i) { + static const int npredefs = (int)(sizeof(C2_PREDEFS) / sizeof(C2_PREDEFS[0])); + for (int i = 0; i < npredefs; ++i) { if (!strcmp(C2_PREDEFS[i].name, pleaf->tgt)) { pleaf->predef = i; pleaf->type = C2_PREDEFS[i].type; @@ -573,25 +596,6 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { } } - // Alias for predefined targets - if (!pleaf->predef) { -#define TGTFILL(pdefid) \ - (pleaf->predef = pdefid, pleaf->type = C2_PREDEFS[pdefid].type, \ - pleaf->format = C2_PREDEFS[pdefid].format) - - // if (!strcmp("WM_NAME", tgt) || !strcmp("_NET_WM_NAME", tgt)) - // TGTFILL(C2_L_PNAME); -#undef TGTFILL - - // Alias for custom properties -#define TGTFILL(target, type, format) \ - (pleaf->target = strdup(target), pleaf->type = type, pleaf->format = format) - - // if (!strcmp("SOME_ALIAS")) - // TGTFILL("ALIAS_TEXT", C2_L_TSTRING, 32); -#undef TGTFILL - } - C2H_SKIP_SPACES(); // Parse target-on-frame flag @@ -607,27 +611,29 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { C2H_SKIP_SPACES(); - int index = -1; + long index = -1; char *endptr = NULL; index = strtol(pattern + offset, &endptr, 0); - if (!endptr || pattern + offset == endptr) + if (!endptr || pattern + offset == endptr) { c2_error("No index number found after bracket."); - - if (index < 0) + } + if (index < 0) { c2_error("Index number invalid."); - - if (pleaf->predef) + } + if (pleaf->predef != C2_L_PUNDEFINED) { c2_error("Predefined targets can't have index."); + } - pleaf->index = index; - offset = endptr - pattern; + pleaf->index = to_int_checked(index); + offset = to_int_checked(endptr - pattern); C2H_SKIP_SPACES(); - if (']' != pattern[offset]) + if (pattern[offset] != ']') { c2_error("Index end marker not found."); + } ++offset; @@ -641,44 +647,43 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { // Look for format bool hasformat = false; - int format = 0; + long format = 0; { char *endptr = NULL; format = strtol(pattern + offset, &endptr, 0); assert(endptr); - if ((hasformat = (endptr && endptr != pattern + offset))) - offset = endptr - pattern; + if ((hasformat = (endptr && endptr != pattern + offset))) { + offset = to_int_checked(endptr - pattern); + } C2H_SKIP_SPACES(); } // Look for type enum c2_l_type type = C2_L_TUNDEFINED; - { - switch (pattern[offset]) { - case 'w': type = C2_L_TWINDOW; break; - case 'd': type = C2_L_TDRAWABLE; break; - case 'c': type = C2_L_TCARDINAL; break; - case 's': type = C2_L_TSTRING; break; - case 'a': type = C2_L_TATOM; break; - default: c2_error("Invalid type character."); - } + switch (pattern[offset]) { + case 'w': type = C2_L_TWINDOW; break; + case 'd': type = C2_L_TDRAWABLE; break; + case 'c': type = C2_L_TCARDINAL; break; + case 's': type = C2_L_TSTRING; break; + case 'a': type = C2_L_TATOM; break; + default: c2_error("Invalid type character."); + } - if (type) { - if (pleaf->predef) { - log_warn("Type specified for a default target " - "will be ignored."); - } else { - if (pleaf->type && type != pleaf->type) - log_warn("Default type overridden on " - "target."); - pleaf->type = type; - } + if (type) { + if (pleaf->predef != C2_L_PUNDEFINED) { + log_warn("Type specified for a default target " + "will be ignored."); + } else { + if (pleaf->type && type != pleaf->type) + log_warn("Default type overridden on " + "target."); + pleaf->type = type; } - - offset++; - C2H_SKIP_SPACES(); } + offset++; + C2H_SKIP_SPACES(); + // Default format if (!pleaf->format) { switch (pleaf->type) { @@ -692,20 +697,20 @@ static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) { // Write format if (hasformat) { - if (pleaf->predef) - log_warn("Format \"%d\" specified on a default target " + if (pleaf->predef != C2_L_PUNDEFINED) { + log_warn("Format \"%ld\" specified on a default target " "will be ignored.", format); - else if (C2_L_TSTRING == pleaf->type) - log_warn("Format \"%d\" specified on a string target " + } else if (pleaf->type == C2_L_TSTRING) { + log_warn("Format \"%ld\" specified on a string target " "will be ignored.", format); - else { + } else { if (pleaf->format && pleaf->format != format) log_warn("Default format %d overridden on " "target.", pleaf->format); - pleaf->format = format; + pleaf->format = to_int_checked(format); } } } @@ -798,52 +803,51 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) c2_l_t *const pleaf = presult->l; // Exists operator cannot have pattern - if (!pleaf->op) + if (!pleaf->op) { return offset; + } C2H_SKIP_SPACES(); char *endptr = NULL; - // Check for boolean patterns if (!strcmp_wd("true", &pattern[offset])) { pleaf->ptntype = C2_L_PTINT; pleaf->ptnint = true; - offset += strlen("true"); + offset += 4; // length of "true"; } else if (!strcmp_wd("false", &pattern[offset])) { pleaf->ptntype = C2_L_PTINT; pleaf->ptnint = false; - offset += strlen("false"); - } - // Check for integer patterns - else if (pleaf->ptnint = strtol(pattern + offset, &endptr, 0), - pattern + offset != endptr) { + offset += 5; // length of "false"; + } else if (pleaf->ptnint = strtol(pattern + offset, &endptr, 0), + pattern + offset != endptr) { pleaf->ptntype = C2_L_PTINT; - offset = endptr - pattern; + offset = to_int_checked(endptr - pattern); // Make sure we are stopping at the end of a word - if (isalnum(pattern[offset])) + if (isalnum(pattern[offset])) { c2_error("Trailing characters after a numeric pattern."); - } - // Check for string patterns - else { + } + } else { + // Parse string patterns bool raw = false; char delim = '\0'; // String flags - if ('r' == tolower(pattern[offset])) { + if (tolower(pattern[offset]) == 'r') { raw = true; ++offset; C2H_SKIP_SPACES(); } // Check for delimiters - if ('\"' == pattern[offset] || '\'' == pattern[offset]) { + if (pattern[offset] == '\"' || pattern[offset] == '\'') { pleaf->ptntype = C2_L_PTSTRING; delim = pattern[offset]; ++offset; } - if (C2_L_PTSTRING != pleaf->ptntype) + if (pleaf->ptntype != C2_L_PTSTRING) { c2_error("Invalid pattern type."); + } // Parse the string now // We can't determine the length of the pattern, so we use the length @@ -876,8 +880,7 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) if (pstr != &tstr[2] || val <= 0) c2_error("Invalid octal/hex escape " "sequence."); - assert(val < 256 && val >= 0); - *(ptptnstr++) = val; + *(ptptnstr++) = to_char_checked(val); offset += 2; break; } @@ -929,10 +932,10 @@ static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) * Parse a condition with legacy syntax. */ static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) { - unsigned plen = strlen(pattern + offset); - - if (plen < 4 || ':' != pattern[offset + 1] || !strchr(pattern + offset + 2, ':')) + if (strlen(pattern + offset) < 4 || pattern[offset + 1] != ':' || + !strchr(pattern + offset + 2, ':')) { c2_error("Legacy parser: Invalid format."); + } // Allocate memory for new leaf auto pleaf = cmalloc(c2_l_t); @@ -1000,8 +1003,8 @@ static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) { } // Get target atom if it's not a predefined one - if (!pleaf->predef) { - pleaf->tgtatom = get_atom(ps, pleaf->tgt); + if (pleaf->predef == C2_L_PUNDEFINED) { + pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt); if (!pleaf->tgtatom) { log_error("Failed to get atom for target \"%s\".", pleaf->tgt); return false; @@ -1027,20 +1030,17 @@ static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) { // Enable specific tracking options in compton if needed by the condition // TODO: Add track_leader - if (pleaf->predef) { - switch (pleaf->predef) { - case C2_L_PFOCUSED: ps->o.track_focus = true; break; - // case C2_L_PROUNDED: ps->o.detect_rounded_corners = true; break; - case C2_L_PNAME: - case C2_L_PCLASSG: - case C2_L_PCLASSI: - case C2_L_PROLE: ps->o.track_wdata = true; break; - default: break; - } + switch (pleaf->predef) { + // case C2_L_PROUNDED: ps->o.detect_rounded_corners = true; break; + case C2_L_PNAME: + case C2_L_PCLASSG: + case C2_L_PCLASSI: + case C2_L_PROLE: ps->o.track_wdata = true; break; + default: break; } // Warn about lower case characters in target name - if (!pleaf->predef) { + if (pleaf->predef == C2_L_PUNDEFINED) { for (const char *pc = pleaf->tgt; *pc; ++pc) { if (islower(*pc)) { log_warn("Lowercase character in target name \"%s\".", @@ -1160,10 +1160,11 @@ c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) { * Get a string representation of a rule target. */ static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) { - if (pleaf->predef) + if (pleaf->predef != C2_L_PUNDEFINED) { return C2_PREDEFS[pleaf->predef].name; - else + } else { return pleaf->tgt; + } } /** @@ -1291,15 +1292,16 @@ static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) { * * For internal use. */ -static inline void -c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, bool *perr) { +static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w, + const c2_l_t *pleaf, bool *pres, bool *perr) { assert(pleaf); - const xcb_window_t wid = (pleaf->tgt_onframe ? w->client_win : w->id); + const xcb_window_t wid = (pleaf->tgt_onframe ? w->client_win : w->base.id); // Return if wid is missing - if (!pleaf->predef && !wid) + if (pleaf->predef == C2_L_PUNDEFINED && !wid) { return; + } const int idx = (pleaf->index < 0 ? 0 : pleaf->index); @@ -1310,7 +1312,7 @@ c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, // Get the value // A predefined target - if (pleaf->predef) { + if (pleaf->predef != C2_L_PUNDEFINED) { *perr = false; switch (pleaf->predef) { case C2_L_PID: tgt = wid; break; @@ -1355,7 +1357,9 @@ c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, // Do comparison switch (pleaf->op) { - case C2_L_OEXISTS: *pres = (pleaf->predef ? tgt : true); break; + case C2_L_OEXISTS: + *pres = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true); + break; case C2_L_OEQ: *pres = (tgt == pleaf->ptnint); break; case C2_L_OGT: *pres = (tgt > pleaf->ptnint); break; case C2_L_OGTEQ: *pres = (tgt >= pleaf->ptnint); break; @@ -1373,7 +1377,7 @@ c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, char *tgt_free = NULL; // A predefined target - if (pleaf->predef) { + if (pleaf->predef != C2_L_PUNDEFINED) { switch (pleaf->predef) { case C2_L_PWINDOWTYPE: tgt = WINTYPES[w->window_type]; break; case C2_L_PNAME: tgt = w->name; break; @@ -1382,20 +1386,19 @@ c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, case C2_L_PROLE: tgt = w->role; break; default: assert(0); break; } - } - // If it's an atom type property, convert atom to string - else if (C2_L_TATOM == pleaf->type) { + } else if (pleaf->type == C2_L_TATOM) { + // An atom type property, convert it to string winprop_t prop = wid_get_prop_adv(ps, wid, pleaf->tgtatom, idx, 1L, c2_get_atom_type(pleaf), pleaf->format); - xcb_atom_t atom = winprop_get_int(prop); + xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop); if (atom) { xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply( ps->c, xcb_get_atom_name(ps->c, atom), NULL); if (reply) { - tgt_free = - strndup(xcb_get_atom_name_name(reply), - xcb_get_atom_name_name_length(reply)); + tgt_free = strndup( + xcb_get_atom_name_name(reply), + (size_t)xcb_get_atom_name_name_length(reply)); free(reply); } } @@ -1403,9 +1406,8 @@ c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, tgt = tgt_free; } free_winprop(&prop); - } - // Otherwise, just fetch the string list - else { + } else { + // Not an atom type, just fetch the string list char **strlst = NULL; int nstr; if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr) && @@ -1456,8 +1458,10 @@ c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, } break; case C2_L_MPCRE: #ifdef CONFIG_REGEX_PCRE - *pres = (pcre_exec(pleaf->regex_pcre, pleaf->regex_pcre_extra, - tgt, strlen(tgt), 0, 0, NULL, 0) >= 0); + assert(strlen(tgt) <= INT_MAX); + *pres = + (pcre_exec(pleaf->regex_pcre, pleaf->regex_pcre_extra, + tgt, (int)strlen(tgt), 0, 0, NULL, 0) >= 0); #else assert(0); #endif @@ -1470,7 +1474,7 @@ c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, // Free the string after usage, if necessary if (tgt_free) { if (C2_L_TATOM == pleaf->type) - cxfree(tgt_free); + XFree(tgt_free); else free(tgt_free); } @@ -1484,7 +1488,7 @@ c2_match_once_leaf(session_t *ps, const win *w, const c2_l_t *pleaf, bool *pres, * * @return true if matched, false otherwise. */ -static bool c2_match_once(session_t *ps, const win *w, const c2_ptr_t cond) { +static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond) { bool result = false; bool error = true; @@ -1558,7 +1562,7 @@ static bool c2_match_once(session_t *ps, const win *w, const c2_ptr_t cond) { * @param pdata a place to return the data * @return true if matched, false otherwise. */ -bool c2_match(session_t *ps, const win *w, const c2_lptr_t *condlst, void **pdata) { +bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata) { // Then go through the whole linked list for (; condlst; condlst = condlst->next) { if (c2_match_once(ps, w, condlst->ptr)) { diff --git a/src/c2.h b/src/c2.h index 21eb112a3e..d6b1d3726c 100644 --- a/src/c2.h +++ b/src/c2.h @@ -15,12 +15,12 @@ typedef struct _c2_lptr c2_lptr_t; typedef struct session session_t; -typedef struct win win; +struct managed_win; c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); c2_lptr_t *c2_free_lptr(c2_lptr_t *lp); -bool c2_match(session_t *ps, const win *w, const c2_lptr_t *condlst, void **pdata); +bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata); bool c2_list_postprocess(session_t *ps, c2_lptr_t *list); diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000000..d1ddda725c --- /dev/null +++ b/src/cache.c @@ -0,0 +1,83 @@ +#include + +#include "utils.h" +#include "cache.h" + +struct cache_entry { + char *key; + void *value; + UT_hash_handle hh; +}; + +struct cache { + cache_getter_t getter; + cache_free_t free; + void *user_data; + struct cache_entry *entries; +}; + +void *cache_get(struct cache *c, const char *key, int *err) { + struct cache_entry *e; + HASH_FIND_STR(c->entries, key, e); + if (e) { + return e->value; + } + + int tmperr; + if (!err) { + err = &tmperr; + } + + *err = 0; + e = ccalloc(1, struct cache_entry); + e->key = strdup(key); + e->value = c->getter(c->user_data, key, err); + if (*err) { + free(e->key); + free(e); + return NULL; + } + + HASH_ADD_STR(c->entries, key, e); + return e->value; +} + +static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) { + if (c->free) { + c->free(c->user_data, e->value); + } + free(e->key); + HASH_DEL(c->entries, e); + free(e); +} + +void cache_invalidate(struct cache *c, const char *key) { + struct cache_entry *e; + HASH_FIND_STR(c->entries, key, e); + + if (e) { + _cache_invalidate(c, e); + } +} + +void cache_invalidate_all(struct cache *c) { + struct cache_entry *e, *tmpe; + HASH_ITER(hh, c->entries, e, tmpe) { + _cache_invalidate(c, e); + } +} + +void *cache_free(struct cache *c) { + void *ret = c->user_data; + cache_invalidate_all(c); + free(c); + return ret; +} + +struct cache *new_cache(void *ud, cache_getter_t getter, cache_free_t f) { + auto c = ccalloc(1, struct cache); + c->user_data = ud; + c->getter = getter; + c->free = f; + return c; +} diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 0000000000..5876ee21fd --- /dev/null +++ b/src/cache.h @@ -0,0 +1,14 @@ +#pragma once + +struct cache; + +typedef void *(*cache_getter_t)(void *user_data, const char *key, int *err); +typedef void (*cache_free_t)(void *user_data, void *data); +struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f); + +void *cache_get(struct cache *, const char *key, int *err); +void cache_invalidate(struct cache *, const char *key); +void cache_invalidate_all(struct cache *); + +/// Returns the user data passed to `new_cache` +void *cache_free(struct cache *); diff --git a/src/common.h b/src/common.h index 0776a0143a..22d9c286d5 100644 --- a/src/common.h +++ b/src/common.h @@ -29,47 +29,19 @@ // For some special functions #include -#include -#include #include -#include -#include -#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include +#include "uthash_extra.h" #ifdef CONFIG_OPENGL -// libGL #include "backend/gl/glx.h" - -// Workarounds for missing definitions in some broken GL drivers, thanks to -// douglasp and consolers for reporting -#ifndef GL_TEXTURE_RECTANGLE -#define GL_TEXTURE_RECTANGLE 0x84F5 -#endif - -#ifndef GLX_BACK_BUFFER_AGE_EXT -#define GLX_BACK_BUFFER_AGE_EXT 0x20F4 -#endif - #endif -// === Macros === - -#define MSTR_(s) #s -#define MSTR(s) MSTR_(s) - // X resource checker #ifdef DEBUG_XRC #include "xrescheck.h" @@ -77,39 +49,20 @@ // FIXME This list of includes should get shorter #include "backend/backend.h" +#include "backend/driver.h" #include "compiler.h" #include "config.h" -#include "kernel.h" -#include "log.h" #include "region.h" -#include "render.h" #include "types.h" #include "utils.h" -#include "win.h" #include "x.h" -// === Constants === - -/// @brief Length of generic buffers. -#define BUF_LEN 80 - -#define ROUNDED_PERCENT 0.05 -#define ROUNDED_PIXELS 10 - -#define REGISTER_PROP "_NET_WM_CM_S" - -#define TIME_MS_MAX LONG_MAX -#define SWOPTI_TOLERANCE 3000 -#define WIN_GET_LEADER_MAX_RECURSION 20 +// === Constants ===0 #define NS_PER_SEC 1000000000L #define US_PER_SEC 1000000L #define MS_PER_SEC 1000 -#define XRFILTER_CONVOLUTION "convolution" -#define XRFILTER_GAUSSIAN "gaussian" -#define XRFILTER_BINOMIAL "binomial" - /// @brief Maximum OpenGL FBConfig depth. #define OPENGL_MAX_DEPTH 32 @@ -118,55 +71,16 @@ // Window flags -// Window size is changed -#define WFLAG_SIZE_CHANGE 0x0001 -// Window size/position is changed -#define WFLAG_POS_CHANGE 0x0002 -// Window opacity / dim state changed -#define WFLAG_OPCT_CHANGE 0x0004 - // === Types === typedef struct glx_fbconfig glx_fbconfig_t; - -/// Structure representing needed window updates. -typedef struct { - bool shadow : 1; - bool fade : 1; - bool focus : 1; - bool invert_color : 1; -} win_upd_t; +struct glx_session; +struct atom; typedef struct _ignore { struct _ignore *next; unsigned long sequence; } ignore_t; -enum wincond_target { - CONDTGT_NAME, - CONDTGT_CLASSI, - CONDTGT_CLASSG, - CONDTGT_ROLE, -}; - -enum wincond_type { - CONDTP_EXACT, - CONDTP_ANYWHERE, - CONDTP_FROMSTART, - CONDTP_WILDCARD, - CONDTP_REGEX_PCRE, -}; - -#define CONDF_IGNORECASE 0x0001 - -/// @brief Possible swap methods. -enum { SWAPM_BUFFER_AGE = -1, - SWAPM_UNDEFINED = 0, - SWAPM_COPY = 1, - SWAPM_EXCHANGE = 2, -}; - -typedef struct _glx_texture glx_texture_t; - #ifdef CONFIG_OPENGL #ifdef DEBUG_GLX_DEBUG_CONTEXT typedef GLXContext (*f_glXCreateContextAttribsARB)(Display *dpy, GLXFBConfig config, @@ -177,41 +91,6 @@ typedef void (*GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severi typedef void (*f_DebugMessageCallback)(GLDEBUGPROC, void *userParam); #endif -#ifdef CONFIG_OPENGL -typedef GLsync (*f_FenceSync)(GLenum condition, GLbitfield flags); -typedef GLboolean (*f_IsSync)(GLsync sync); -typedef void (*f_DeleteSync)(GLsync sync); -typedef GLenum (*f_ClientWaitSync)(GLsync sync, GLbitfield flags, GLuint64 timeout); -typedef void (*f_WaitSync)(GLsync sync, GLbitfield flags, GLuint64 timeout); -typedef GLsync (*f_ImportSyncEXT)(GLenum external_sync_type, GLintptr external_sync, - GLbitfield flags); -#endif - -/// @brief Wrapper of a binded GLX texture. -struct _glx_texture { - GLuint texture; - GLXPixmap glpixmap; - xcb_pixmap_t pixmap; - GLenum target; - unsigned width; - unsigned height; - bool y_inverted; -}; - -#ifdef CONFIG_OPENGL -typedef struct { - /// Fragment shader for blur. - GLuint frag_shader; - /// GLSL program for blur. - GLuint prog; - /// Location of uniform "offset_x" in blur GLSL program. - GLint unifm_offset_x; - /// Location of uniform "offset_y" in blur GLSL program. - GLint unifm_offset_y; - /// Location of uniform "factor_center" in blur GLSL program. - GLint unifm_factor_center; -} glx_blur_pass_t; - typedef struct glx_prog_main { /// GLSL program. GLuint prog; @@ -226,7 +105,6 @@ typedef struct glx_prog_main { #define GLX_PROG_MAIN_INIT \ { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } -#endif #else struct glx_prog_main {}; #endif @@ -240,41 +118,6 @@ typedef struct _latom { struct _latom *next; } latom_t; -#define REG_DATA_INIT \ - { NULL, 0 } - -#ifdef CONFIG_OPENGL -/// Structure containing GLX-dependent data for a compton session. -typedef struct { - // === OpenGL related === - /// GLX context. - GLXContext context; - /// Whether we have GL_ARB_texture_non_power_of_two. - bool has_texture_non_power_of_two; - /// Pointer to the glFenceSync() function. - f_FenceSync glFenceSyncProc; - /// Pointer to the glIsSync() function. - f_IsSync glIsSyncProc; - /// Pointer to the glDeleteSync() function. - f_DeleteSync glDeleteSyncProc; - /// Pointer to the glClientWaitSync() function. - f_ClientWaitSync glClientWaitSyncProc; - /// Pointer to the glWaitSync() function. - f_WaitSync glWaitSyncProc; - /// Pointer to the glImportSyncEXT() function. - f_ImportSyncEXT glImportSyncEXT; - /// Current GLX Z value. - int z; -#ifdef CONFIG_OPENGL - glx_blur_pass_t blur_passes[MAX_BLUR_PASS]; -#endif -} glx_session_t; - -#define CGLX_SESSION_INIT \ - { .context = NULL } - -#endif - /// Structure containing all necessary data for a compton session. typedef struct session { // === Event handlers === @@ -300,6 +143,10 @@ typedef struct session { ev_signal int_signal; /// backend data backend_t *backend_data; + /// backend blur context + void *backend_blur_context; + /// graphic drivers used + enum driver drivers; /// libev mainloop struct ev_loop *loop; @@ -324,6 +171,8 @@ typedef struct session { // Damage root_damage; /// X Composite overlay window. Used if --paint-on-overlay. xcb_window_t overlay; + /// The target window for debug mode + xcb_window_t debug_window; /// Whether the root tile is filled by compton. bool root_tile_fill; /// Picture of the root window background. @@ -343,15 +192,17 @@ typedef struct session { xcb_window_t reg_win; #ifdef CONFIG_OPENGL /// Pointer to GLX data. - glx_session_t *psglx; + struct glx_session *psglx; /// Custom GLX program used for painting window. - // XXX should be in glx_session_t + // XXX should be in struct glx_session glx_prog_main_t glx_prog_win; #endif /// Sync fence to sync draw operations xcb_sync_fence_t sync_fence; // === Operation related === + /// Flags related to the root window + uint64_t root_flags; /// Program options. options_t o; /// Whether we have hit unredirection timeout. @@ -371,18 +222,22 @@ typedef struct session { /// Pre-generated alpha pictures. xcb_render_picture_t *alpha_picts; /// Time of last fading. In milliseconds. - unsigned long fade_time; + long fade_time; /// Head pointer of the error ignore linked list. ignore_t *ignore_head; /// Pointer to the next member of tail element of the error /// ignore linked list. ignore_t **ignore_tail; // Cached blur convolution kernels. - xcb_render_fixed_t *blur_kerns_cache[MAX_BLUR_PASS]; + struct x_convolution_kernel **blur_kerns_cache; /// Reset program after next paint. - bool reset; + bool reset:1; /// If compton should quit - bool quit; + bool quit:1; + /// Whether there are pending updates, like window creation, etc. + /// TODO use separate flags for dfferent kinds of updates so we don't + /// waste our time. + bool pending_updates:1; // === Expose event related === /// Pointer to an array of XRectangle-s of exposed region. @@ -394,14 +249,16 @@ typedef struct session { int n_expose; // === Window related === - /// Linked list of all windows. - win *list; + /// A hash table of all windows. + struct win *windows; + /// Windows in their stacking order + struct list_node window_stack; /// Pointer to win of current active window. Used by /// EWMH _NET_ACTIVE_WINDOW focus detection. In theory, /// it's more reliable to store the window ID directly here, just in /// case the WM does something extraordinary, but caching the pointer /// means another layer of complexity. - win *active_win; + struct managed_win *active_win; /// Window ID of leader window of currently active window. Used for /// subsidiary window detection. xcb_window_t active_leader; @@ -421,7 +278,7 @@ typedef struct session { // === Software-optimization-related === /// Currently used refresh rate. - short refresh_rate; + int refresh_rate; /// Interval between refresh in nanoseconds. long refresh_intv; /// Nanosecond offset of the first painting. @@ -476,8 +333,6 @@ typedef struct session { #endif /// Whether X Xinerama extension exists. bool xinerama_exists; - /// Xinerama screen info. - xcb_xinerama_query_screens_reply_t *xinerama_scrs; /// Xinerama screen regions. region_t *xinerama_scr_regs; /// Number of Xinerama screens. @@ -492,31 +347,7 @@ typedef struct session { bool xrfilter_convolution_exists; // === Atoms === - /// Atom of property _NET_WM_OPACITY. - xcb_atom_t atom_opacity; - /// Atom of _NET_FRAME_EXTENTS. - xcb_atom_t atom_frame_extents; - /// Property atom to identify top-level frame window. Currently - /// WM_STATE. - xcb_atom_t atom_client; - /// Atom of property WM_NAME. - xcb_atom_t atom_name; - /// Atom of property _NET_WM_NAME. - xcb_atom_t atom_name_ewmh; - /// Atom of property WM_CLASS. - xcb_atom_t atom_class; - /// Atom of property WM_WINDOW_ROLE. - xcb_atom_t atom_role; - /// Atom of property WM_TRANSIENT_FOR. - xcb_atom_t atom_transient; - /// Atom of property WM_CLIENT_LEADER. - xcb_atom_t atom_client_leader; - /// Atom of property _NET_ACTIVE_WINDOW. - xcb_atom_t atom_ewmh_active_win; - /// Atom of property _COMPTON_SHADOW. - xcb_atom_t atom_compton_shadow; - /// Atom of property _NET_WM_WINDOW_TYPE. - xcb_atom_t atom_win_type; + struct atom *atoms; /// Array of atoms of all possible window types. xcb_atom_t atoms_wintypes[NUM_WINTYPES]; /// Linked list of additional atoms to track. @@ -530,99 +361,16 @@ typedef struct session { int (*vsync_wait)(session_t *); } session_t; -/// Temporary structure used for communication between -/// get_cfg() and parse_config(). -struct options_tmp { - bool no_dock_shadow; - bool no_dnd_shadow; - double menu_opacity; -}; - /// Enumeration for window event hints. typedef enum { WIN_EVMODE_UNKNOWN, WIN_EVMODE_FRAME, WIN_EVMODE_CLIENT } win_evmode_t; extern const char *const WINTYPES[NUM_WINTYPES]; extern session_t *ps_g; -// == Debugging code == -static inline void print_timestamp(session_t *ps); - void ev_xcb_error(session_t *ps, xcb_generic_error_t *err); // === Functions === -/** - * Return whether a struct timeval value is empty. - */ -static inline bool timeval_isempty(struct timeval *ptv) { - if (!ptv) - return false; - - return ptv->tv_sec <= 0 && ptv->tv_usec <= 0; -} - -/** - * Compare a struct timeval with a time in milliseconds. - * - * @return > 0 if ptv > ms, 0 if ptv == 0, -1 if ptv < ms - */ -static inline int timeval_ms_cmp(struct timeval *ptv, unsigned long ms) { - assert(ptv); - - // We use those if statement instead of a - expression because of possible - // truncation problem from long to int. - { - long sec = ms / MS_PER_SEC; - if (ptv->tv_sec > sec) - return 1; - if (ptv->tv_sec < sec) - return -1; - } - - { - long usec = ms % MS_PER_SEC * (US_PER_SEC / MS_PER_SEC); - if (ptv->tv_usec > usec) - return 1; - if (ptv->tv_usec < usec) - return -1; - } - - return 0; -} - -/** - * Subtracting two struct timeval values. - * - * Taken from glibc manual. - * - * Subtract the `struct timeval' values X and Y, - * storing the result in RESULT. - * Return 1 if the difference is negative, otherwise 0. - */ -static inline int -timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y) { - /* Perform the carry for the later subtraction by updating y. */ - if (x->tv_usec < y->tv_usec) { - long nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; - y->tv_usec -= 1000000 * nsec; - y->tv_sec += nsec; - } - - if (x->tv_usec - y->tv_usec > 1000000) { - long nsec = (x->tv_usec - y->tv_usec) / 1000000; - y->tv_usec += 1000000 * nsec; - y->tv_sec -= nsec; - } - - /* Compute the time remaining to wait. - tv_usec is certainly positive. */ - result->tv_sec = x->tv_sec - y->tv_sec; - result->tv_usec = x->tv_usec - y->tv_usec; - - /* Return 1 if result is negative. */ - return x->tv_sec < y->tv_sec; -} - /** * Subtracting two struct timespec values. * @@ -682,53 +430,6 @@ static inline struct timespec get_time_timespec(void) { return tm; } -/** - * Print time passed since program starts execution. - * - * Used for debugging. - */ -static inline void print_timestamp(session_t *ps) { - struct timeval tm, diff; - - if (gettimeofday(&tm, NULL)) - return; - - timeval_subtract(&diff, &tm, &ps->time_start); - fprintf(stderr, "[ %5ld.%06ld ] ", diff.tv_sec, diff.tv_usec); -} - -/** - * Wrapper of XFree() for convenience. - * - * Because a NULL pointer cannot be passed to XFree(), its man page says. - */ -static inline void cxfree(void *data) { - if (data) - XFree(data); -} - -_Noreturn static inline void die(const char *msg) { - puts(msg); - exit(1); -} - -/** - * Wrapper of XInternAtom() for convenience. - */ -static inline xcb_atom_t get_atom(session_t *ps, const char *atom_name) { - xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply( - ps->c, xcb_intern_atom(ps->c, 0, strlen(atom_name), atom_name), NULL); - - xcb_atom_t atom = XCB_NONE; - if (reply) { - log_debug("Atom %s is %d", atom_name, reply->atom); - atom = reply->atom; - free(reply); - } else - die("Failed to intern atoms, bail out"); - return atom; -} - /** * Return the painting target window. */ @@ -736,43 +437,6 @@ static inline xcb_window_t get_tgt_window(session_t *ps) { return ps->overlay != XCB_NONE ? ps->overlay : ps->root; } -/** - * Find a window from window id in window linked list of the session. - */ -static inline win *find_win(session_t *ps, xcb_window_t id) { - if (!id) - return NULL; - - win *w; - - for (w = ps->list; w; w = w->next) { - if (w->id == id && w->state != WSTATE_DESTROYING) { - return w; - } - } - - return 0; -} - -/** - * Find out the WM frame of a client window using existing data. - * - * @param id window ID - * @return struct win object of the found window, NULL if not found - */ -static inline win *find_toplevel(session_t *ps, xcb_window_t id) { - if (!id) - return NULL; - - for (win *w = ps->list; w; w = w->next) { - if (w->client_win == id && w->state != WSTATE_DESTROYING) { - return w; - } - } - - return NULL; -} - /** * Check if current backend uses GLX. */ @@ -780,34 +444,6 @@ static inline bool bkend_use_glx(session_t *ps) { return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend; } -/** - * Check if a window is really focused. - */ -static inline bool win_is_focused_real(session_t *ps, const win *w) { - return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w; -} - -/** - * Find out the currently focused window. - * - * @return struct win object of the found window, NULL if not found - */ -static inline win *find_focused(session_t *ps) { - if (!ps->o.track_focus) - return NULL; - - if (ps->active_win && win_is_focused_real(ps, ps->active_win)) - return ps->active_win; - return NULL; -} - -/** - * Check if a rectangle includes the whole screen. - */ -static inline bool rect_is_fullscreen(session_t *ps, int x, int y, int wid, int hei) { - return (x <= 0 && y <= 0 && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height); -} - static void set_ignore(session_t *ps, unsigned long sequence) { if (ps->o.show_all_xerrors) return; @@ -829,23 +465,6 @@ static inline void set_ignore_cookie(session_t *ps, xcb_void_cookie_t cookie) { set_ignore(ps, cookie.sequence); } -/** - * Check if a window is a fullscreen window. - * - * It's not using w->border_size for performance measures. - */ -static inline bool win_is_fullscreen(session_t *ps, const win *w) { - return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) && - (!w->bounding_shaped || w->rounded_corners); -} - -/** - * Check if a window will be painted solid. - */ -static inline bool win_is_solid(session_t *ps, const win *w) { - return WMODE_SOLID == w->mode && !ps->o.force_win_blend; -} - /** * Determine if a window has a specific property. * @@ -870,31 +489,8 @@ static inline bool wid_has_prop(const session_t *ps, xcb_window_t w, xcb_atom_t return false; } -/** - * Get the numeric property value from a win_prop_t. - */ -static inline long winprop_get_int(winprop_t prop) { - long tgt = 0; - - if (!prop.nitems) - return 0; - - switch (prop.format) { - case 8: tgt = *(prop.p8); break; - case 16: tgt = *(prop.p16); break; - case 32: tgt = *(prop.p32); break; - default: assert(0); break; - } - - return tgt; -} - void force_repaint(session_t *ps); -bool vsync_init(session_t *ps); - -void vsync_deinit(session_t *ps); - /** @name DBus handling */ ///@{ @@ -902,16 +498,6 @@ void vsync_deinit(session_t *ps); /** @name DBus hooks */ ///@{ -void win_set_shadow_force(session_t *ps, win *w, switch_t val); - -void win_set_fade_force(session_t *ps, win *w, switch_t val); - -void win_set_focused_force(session_t *ps, win *w, switch_t val); - -void win_set_invert_color_force(session_t *ps, win *w, switch_t val); - -void opts_init_track_focus(session_t *ps); - void opts_set_no_fading_openclose(session_t *ps, bool newval); //!@} #endif diff --git a/src/compiler.h b/src/compiler.h index b44562b261..8d8722e5b1 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -2,7 +2,9 @@ // Copyright (c) 2018 Yuxuan Shui #pragma once +#ifdef HAS_STDC_PREDEF_H #include +#endif #define auto __auto_type #define likely(x) __builtin_expect(!!(x), 1) @@ -64,6 +66,12 @@ # define attr_ret_nonnull #endif +#if __has_attribute(deprecated) +# define attr_deprecated __attribute__((deprecated)) +#else +# define attr_deprecated +#endif + #if __has_attribute(malloc) # define attr_malloc __attribute__((malloc)) #else @@ -95,3 +103,6 @@ #else # define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__ #endif + +typedef unsigned long ulong; +typedef unsigned int uint; diff --git a/src/compton.c b/src/compton.c index 0124978a63..a04c4a6c74 100644 --- a/src/compton.c +++ b/src/compton.c @@ -11,8 +11,6 @@ #include #include -#include -#include #include #include #include @@ -25,12 +23,15 @@ #include #include #include +#include #include +#include #include "common.h" #include "compiler.h" #include "compton.h" +#include "config.h" #include "err.h" #include "kernel.h" #ifdef CONFIG_OPENGL @@ -50,10 +51,11 @@ #ifdef CONFIG_DBUS #include "dbus.h" #endif +#include "atom.h" +#include "event.h" +#include "list.h" #include "options.h" - -#define CASESTRRET(s) \ - case s: return #s +#include "uthash_extra.h" /// Get session_t pointer from a pointer to a member of session_t #define session_ptr(ptr, member) \ @@ -62,28 +64,12 @@ (session_t *)((char *)__mptr - offsetof(session_t, member)); \ }) -static void update_refresh_rate(session_t *ps); - -static bool swopti_init(session_t *ps); - -static void cxinerama_upd_scrs(session_t *ps); - -static void session_destroy(session_t *ps); - -static void cxinerama_upd_scrs(session_t *ps); +static const long SWOPTI_TOLERANCE = 3000; static bool must_use redir_start(session_t *ps); static void redir_stop(session_t *ps); -static win *recheck_focus(session_t *ps); - -static void restack_win(session_t *ps, win *w, xcb_window_t new_above); - -static void update_ewmh_active_win(session_t *ps); - -static void draw_callback(EV_P_ ev_idle *w, int revents); - // === Global constants === /// Name strings for window types. @@ -106,6 +92,15 @@ const char *const BACKEND_STRS[NUM_BKEND + 1] = {"xrender", // BKEN /// XXX Limit what xerror can access by not having this pointer session_t *ps_g = NULL; +void set_root_flags(session_t *ps, uint64_t flags) { + ps->root_flags |= flags; +} + +static inline void quit_compton(session_t *ps) { + ps->quit = true; + ev_break(ps->loop, EVBREAK_ALL); +} + /** * Free Xinerama screen info. * @@ -117,22 +112,20 @@ static inline void free_xinerama_info(session_t *ps) { pixman_region32_fini(&ps->xinerama_scr_regs[i]); free(ps->xinerama_scr_regs); } - cxfree(ps->xinerama_scrs); - ps->xinerama_scrs = NULL; ps->xinerama_nscrs = 0; } /** * Get current system clock in milliseconds. */ -static inline uint64_t get_time_ms(void) { +static inline int64_t get_time_ms(void) { struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); - return tp.tv_sec * ((uint64_t)1000ul) + tp.tv_nsec / 1000000; + return (int64_t)tp.tv_sec * 1000 + (int64_t)tp.tv_nsec / 1000000; } // XXX Move to x.c -static void cxinerama_upd_scrs(session_t *ps) { +void cxinerama_upd_scrs(session_t *ps) { // XXX Consider deprecating Xinerama, switch to RandR when necessary free_xinerama_info(ps); @@ -147,14 +140,15 @@ static void cxinerama_upd_scrs(session_t *ps) { } free(active); - ps->xinerama_scrs = + auto xinerama_scrs = xcb_xinerama_query_screens_reply(ps->c, xcb_xinerama_query_screens(ps->c), NULL); - if (!ps->xinerama_scrs) + if (!xinerama_scrs) { return; + } xcb_xinerama_screen_info_t *scrs = - xcb_xinerama_query_screens_screen_info(ps->xinerama_scrs); - ps->xinerama_nscrs = xcb_xinerama_query_screens_screen_info_length(ps->xinerama_scrs); + xcb_xinerama_query_screens_screen_info(xinerama_scrs); + ps->xinerama_nscrs = xcb_xinerama_query_screens_screen_info_length(xinerama_scrs); ps->xinerama_scr_regs = ccalloc(ps->xinerama_nscrs, region_t); for (int i = 0; i < ps->xinerama_nscrs; ++i) { @@ -162,6 +156,7 @@ static void cxinerama_upd_scrs(session_t *ps) { pixman_region32_init_rect(&ps->xinerama_scr_regs[i], s->x_org, s->y_org, s->width, s->height); } + free(xinerama_scrs); } /** @@ -169,11 +164,11 @@ static void cxinerama_upd_scrs(session_t *ps) { * * XXX move to win.c */ -static inline win *find_win_all(session_t *ps, const xcb_window_t wid) { +static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t wid) { if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay) return NULL; - win *w = find_win(ps, wid); + auto w = find_managed_win(ps, wid); if (!w) w = find_toplevel(ps, wid); if (!w) @@ -219,11 +214,11 @@ static double fade_timeout(session_t *ps) { if (ps->o.fade_delta + ps->fade_time < now) return 0; - int diff = ps->o.fade_delta + ps->fade_time - now; + auto diff = ps->o.fade_delta + ps->fade_time - now; - diff = normalize_i_range(diff, 0, ps->o.fade_delta * 2); + diff = clamp(diff, 0, ps->o.fade_delta * 2); - return diff / 1000.0; + return (double)diff / 1000.0; } /** @@ -232,8 +227,8 @@ static double fade_timeout(session_t *ps) { * @param steps steps of fading * @return whether we are still in fading mode */ -static bool run_fade(session_t *ps, win **_w, unsigned steps) { - win *w = *_w; +static bool run_fade(session_t *ps, struct managed_win **_w, long steps) { + auto w = *_w; if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) { // We are not fading assert(w->opacity_tgt == w->opacity); @@ -241,37 +236,35 @@ static bool run_fade(session_t *ps, win **_w, unsigned steps) { } if (!win_should_fade(ps, w)) { - log_debug("Window %#010x %s doesn't need fading", w->id, w->name); + log_debug("Window %#010x %s doesn't need fading", w->base.id, w->name); w->opacity = w->opacity_tgt; } if (w->opacity == w->opacity_tgt) { - // We have reached target opacity, wrapping up. - // Note, we reach here after we have rendered the window with the target - // opacity at least once. If the window is destroyed because it is faded - // out, there is no need to add damage here. - log_debug("Fading finished for window %#010x %s", w->id, w->name); - win_check_fade_finished(ps, _w); + // We have reached target opacity. + // We don't call win_check_fade_finished here because that could destroy + // the window, but we still need the damage info from this window + log_debug("Fading finished for window %#010x %s", w->base.id, w->name); return false; } if (steps) { if (w->opacity < w->opacity_tgt) { - w->opacity = normalize_d_range( - w->opacity + ps->o.fade_in_step * steps, 0.0, w->opacity_tgt); + w->opacity = clamp(w->opacity + ps->o.fade_in_step * (double)steps, + 0.0, w->opacity_tgt); } else { - w->opacity = normalize_d_range( - w->opacity - ps->o.fade_out_step * steps, w->opacity_tgt, 1); + w->opacity = clamp(w->opacity - ps->o.fade_out_step * (double)steps, + w->opacity_tgt, 1); } } - // Note even if opacity == opacity_tgt, we still want to run preprocess one last - // time to finish state transition. So return true in that case too. + // Note even if opacity == opacity_tgt here, we still want to run preprocess one + // last time to finish state transition. So return true in that case too. return true; } // === Error handling === -static void discard_ignore(session_t *ps, unsigned long sequence) { +void discard_ignore(session_t *ps, unsigned long sequence) { while (ps->ignore_head) { if (sequence > ps->ignore_head->sequence) { ignore_t *next = ps->ignore_head->next; @@ -296,22 +289,23 @@ static int should_ignore(session_t *ps, unsigned long sequence) { /** * Determine the event mask for a window. */ -long determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode) { - long evmask = 0; - win *w = NULL; +uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode) { + uint32_t evmask = 0; + struct managed_win *w = NULL; // Check if it's a mapped frame window if (WIN_EVMODE_FRAME == mode || - ((w = find_win(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) { + ((w = find_managed_win(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) { evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE; - if (ps->o.track_focus && !ps->o.use_ewmh_active_win) + if (!ps->o.use_ewmh_active_win) { evmask |= XCB_EVENT_MASK_FOCUS_CHANGE; + } } // Check if it's a mapped client window if (WIN_EVMODE_CLIENT == mode || ((w = find_toplevel(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) { - if (ps->o.frame_opacity || ps->o.track_wdata || ps->track_atom_lst || + if (ps->o.frame_opacity > 0 || ps->o.track_wdata || ps->track_atom_lst || ps->o.detect_client_opacity) evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE; } @@ -320,35 +314,20 @@ long determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode) { } /** - * Find out the WM frame of a client window by querying X. + * Update current active window based on EWMH _NET_ACTIVE_WIN. * - * @param ps current session - * @param wid window ID - * @return struct _win object of the found window, NULL if not found + * Does not change anything if we fail to get the attribute or the window + * returned could not be found. */ -win *find_toplevel2(session_t *ps, xcb_window_t wid) { - // TODO this should probably be an "update tree", then find_toplevel. - // current approach is a bit more "racy" - win *w = NULL; - - // We traverse through its ancestors to find out the frame - while (wid && wid != ps->root && !(w = find_win(ps, wid))) { - xcb_query_tree_reply_t *reply; - - // xcb_query_tree probably fails if you run compton when X is somehow - // initializing (like add it in .xinitrc). In this case - // just leave it alone. - reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL); - if (reply == NULL) { - break; - } - - wid = reply->parent; +void update_ewmh_active_win(session_t *ps) { + // Search for the window + xcb_window_t wid = wid_get_prop_window(ps, ps->root, ps->atoms->a_NET_ACTIVE_WINDOW); + auto w = find_win_all(ps, wid); - free(reply); + // Mark the window focused. No need to unfocus the previous one. + if (w) { + win_set_focused(ps, w); } - - return w; } /** @@ -358,11 +337,11 @@ win *find_toplevel2(session_t *ps, xcb_window_t wid) { * @param ps current session * @return struct _win of currently focused window, NULL if not found */ -static win *recheck_focus(session_t *ps) { +static void recheck_focus(session_t *ps) { // Use EWMH _NET_ACTIVE_WINDOW if enabled if (ps->o.use_ewmh_active_win) { update_ewmh_active_win(ps); - return ps->active_win; + return; } // Determine the currently focused window so we can apply appropriate @@ -376,25 +355,23 @@ static win *recheck_focus(session_t *ps) { free(reply); } - win *w = find_win_all(ps, wid); + auto w = find_win_all(ps, wid); log_trace("%#010" PRIx32 " (%#010lx \"%s\") focused.", wid, - (w ? w->id : XCB_NONE), (w ? w->name : NULL)); + (w ? w->base.id : XCB_NONE), (w ? w->name : NULL)); // And we set the focus state here if (w) { - win_set_focused(ps, w, true); - return w; + win_set_focused(ps, w); + return; } - - return NULL; } /** * Look for the client window of a particular window. */ xcb_window_t find_client_win(session_t *ps, xcb_window_t w) { - if (wid_has_prop(ps, w, ps->atom_client)) { + if (wid_has_prop(ps, w, ps->atoms->aWM_STATE)) { return w; } @@ -418,14 +395,31 @@ xcb_window_t find_client_win(session_t *ps, xcb_window_t w) { return ret; } -static win *paint_preprocess(session_t *ps, bool *fade_running) { +static void handle_root_flags(session_t *ps) { + if ((ps->root_flags & ROOT_FLAGS_SCREEN_CHANGE) != 0) { + if (ps->o.xinerama_shadow_crop) { + cxinerama_upd_scrs(ps); + } + + if (ps->o.sw_opti && !ps->o.refresh_rate) { + update_refresh_rate(ps); + if (!ps->refresh_rate) { + log_warn("Refresh rate detection failed. swopti will be " + "temporarily disabled"); + } + } + ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE; + } +} + +static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { // XXX need better, more general name for `fade_running`. It really // means if fade is still ongoing after the current frame is rendered - win *t = NULL, *next = NULL; + struct managed_win *bottom = NULL; *fade_running = false; // Fading step calculation - unsigned long steps = 0L; + long steps = 0L; auto now = get_time_ms(); if (ps->fade_time) { assert(now >= ps->fade_time); @@ -438,11 +432,10 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { ps->fade_time += steps * ps->o.fade_delta; // First, let's process fading - for (win *w = ps->list; w; w = next) { - next = w->next; + win_stack_foreach_managed_safe(w, &ps->window_stack) { const winmode_t mode_old = w->mode; const bool was_painted = w->to_paint; - const opacity_t opacity_old = w->opacity; + const double opacity_old = w->opacity; if (win_should_dim(ps, w) != w->dim) { w->dim = win_should_dim(ps, w); @@ -454,6 +447,16 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { *fade_running = true; } + // Add window to damaged area if its opacity changes + // If was_painted == false, and to_paint is also false, we don't care + // If was_painted == false, but to_paint is true, damage will be added in + // the loop below + if (was_painted && w->opacity != opacity_old) { + add_damage_from_win(ps, w); + } + + win_check_fade_finished(ps, &w); + if (!w) { // the window might have been destroyed because fading finished continue; @@ -473,35 +476,30 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { if (was_painted && w->mode != mode_old) { w->reg_ignore_valid = false; } - - // Add window to damaged area if its opacity changes - // If was_painted == false, and to_paint is also false, we don't care - // If was_painted == false, but to_paint is true, damage will be added in - // the loop below - if (was_painted && w->opacity != opacity_old) { - add_damage_from_win(ps, w); - } } // Opacity will not change, from now on. rc_region_t *last_reg_ignore = rc_region_new(); bool unredir_possible = false; - // Trace whether it's the highest window to paint + // Track whether it's the highest window to paint bool is_highest = true; bool reg_ignore_valid = true; - for (win *w = ps->list; w; w = next) { + win_stack_foreach_managed(w, &ps->window_stack) { __label__ skip_window; bool to_paint = true; // w->to_paint remembers whether this window is painted last time const bool was_painted = w->to_paint; - // In case calling the fade callback function destroys this window - next = w->next; - // Destroy reg_ignore if some window above us invalidated it - if (!reg_ignore_valid) + if (!reg_ignore_valid) { rc_region_unref(&w->reg_ignore); + } + + // Clear flags if we are not using experimental backends + if (!ps->o.experimental_backends) { + w->flags = 0; + } // log_trace("%d %d %s", w->a.map_state, w->ever_damaged, w->name); @@ -511,8 +509,17 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { if (!w->ever_damaged || w->g.x + w->g.width < 1 || w->g.y + w->g.height < 1 || w->g.x >= ps->root_width || w->g.y >= ps->root_height || w->state == WSTATE_UNMAPPED || - (double)w->opacity * MAX_ALPHA < 1 || w->paint_excluded) + (double)w->opacity * MAX_ALPHA < 1 || w->paint_excluded) { + to_paint = false; + } + + if (w->base.id == ps->debug_window || w->client_win == ps->debug_window) { + to_paint = false; + } + + if ((w->flags & WIN_FLAGS_IMAGE_ERROR) != 0) { to_paint = false; + } // log_trace("%s %d %d %d", w->name, to_paint, w->opacity, // w->paint_excluded); @@ -523,16 +530,18 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { add_damage_from_win(ps, w); } - // to_paint will never change afterward - if (!to_paint) + // to_paint will never change after this point + if (!to_paint) { goto skip_window; + } // Calculate shadow opacity w->shadow_opacity = ps->o.shadow_opacity * w->opacity * ps->o.frame_opacity; // Generate ignore region for painting to reduce GPU load - if (!w->reg_ignore) + if (!w->reg_ignore) { w->reg_ignore = rc_region_ref(last_reg_ignore); + } // If the window is solid, we add the window region to the // ignored region @@ -558,16 +567,14 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { // fading is enabled, and could create inconsistency when the wallpaper // is not correctly set. if (ps->o.unredir_if_possible && is_highest) { - if (win_is_solid(ps, w) && - (w->frame_opacity == 1 || !win_has_frame(w)) && - win_is_fullscreen(ps, w) && !w->unredir_if_possible_excluded) + if (w->mode == WMODE_SOLID && !ps->o.force_win_blend && + win_is_fullscreen(ps, w) && !w->unredir_if_possible_excluded) { unredir_possible = true; + } } - // Reset flags - w->flags = 0; - w->prev_trans = t; - t = w; + w->prev_trans = bottom; + bottom = w; // If the screen is not redirected and the window has redir_ignore set, // this window should not cause the screen to become redirected @@ -579,8 +586,6 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { reg_ignore_valid = reg_ignore_valid && w->reg_ignore_valid; w->reg_ignore_valid = true; - win_check_fade_finished(ps, &w); - // Avoid setting w->to_paint if w is freed if (w) { w->to_paint = to_paint; @@ -590,46 +595,36 @@ static win *paint_preprocess(session_t *ps, bool *fade_running) { rc_region_unref(&last_reg_ignore); // If possible, unredirect all windows and stop painting - if (ps->o.redirected_force != UNSET) + if (ps->o.redirected_force != UNSET) { unredir_possible = !ps->o.redirected_force; - else if (ps->o.unredir_if_possible && is_highest && !ps->redirected) + } else if (ps->o.unredir_if_possible && is_highest && !ps->redirected) { // If there's no window to paint, and the screen isn't redirected, // don't redirect it. unredir_possible = true; + } if (unredir_possible) { if (ps->redirected) { if (!ps->o.unredir_if_possible_delay || ps->tmout_unredir_hit) redir_stop(ps); else if (!ev_is_active(&ps->unredir_timer)) { - ev_timer_set(&ps->unredir_timer, - ps->o.unredir_if_possible_delay / 1000.0, 0); + ev_timer_set( + &ps->unredir_timer, + (double)ps->o.unredir_if_possible_delay / 1000.0, 0); ev_timer_start(ps->loop, &ps->unredir_timer); } } } else { ev_timer_stop(ps->loop, &ps->unredir_timer); - if (!redir_start(ps)) { - return NULL; + if (!ps->redirected) { + if (!redir_start(ps)) { + return NULL; + } } } - return t; + return bottom; } -/* - * WORK-IN-PROGRESS! -static void -xr_take_screenshot(session_t *ps) { - XImage *img = XGetImage(ps->dpy, get_tgt_window(ps), 0, 0, - ps->root_width, ps->root_height, AllPlanes, XYPixmap); - if (!img) { - log_error("Failed to get XImage."); - return NULL; - } - assert(0 == img->xoffset); -} -*/ - /** * Rebuild cached screen_reg. */ @@ -646,118 +641,132 @@ static void rebuild_shadow_exclude_reg(session_t *ps) { exit(1); } -static void repair_win(session_t *ps, win *w) { - if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) - return; +/// Free up all the images and deinit the backend +static void destroy_backend(session_t *ps) { + win_stack_foreach_managed_safe(w, &ps->window_stack) { + // Wrapping up fading in progress + win_skip_fading(ps, &w); - region_t parts; - pixman_region32_init(&parts); + // `w` might be freed by win_check_fade_finished + if (!w) { + continue; + } - if (!w->ever_damaged) { - win_extents(w, &parts); - set_ignore_cookie( - ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, XCB_NONE)); - } else { - xcb_xfixes_region_t tmp = xcb_generate_id(ps->c); - xcb_xfixes_create_region(ps->c, tmp, 0, NULL); - set_ignore_cookie(ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, tmp)); - xcb_xfixes_translate_region(ps->c, tmp, w->g.x + w->g.border_width, - w->g.y + w->g.border_width); - x_fetch_region(ps->c, tmp, &parts); - xcb_xfixes_destroy_region(ps->c, tmp); + if (ps->backend_data) { + if (w->state == WSTATE_MAPPED) { + win_release_image(ps->backend_data, w); + } else { + assert(!w->win_image); + assert(!w->shadow_image); + } + if (ps->root_image) { + ps->backend_data->ops->release_image(ps->backend_data, + ps->root_image); + ps->root_image = NULL; + } + } + free_paint(ps, &w->paint); } - w->ever_damaged = true; - w->pixmap_damaged = true; - - // Why care about damage when screen is unredirected? - // We will force full-screen repaint on redirection. - if (!ps->redirected) { - pixman_region32_fini(&parts); - return; + if (ps->backend_data) { + // deinit backend + if (ps->backend_blur_context) { + ps->backend_data->ops->destroy_blur_context( + ps->backend_data, ps->backend_blur_context); + ps->backend_blur_context = NULL; + } + ps->backend_data->ops->deinit(ps->backend_data); + ps->backend_data = NULL; } - - // Remove the part in the damage area that could be ignored - if (w->reg_ignore && win_is_region_ignore_valid(ps, w)) - pixman_region32_subtract(&parts, &parts, w->reg_ignore); - - add_damage(ps, &parts); - pixman_region32_fini(&parts); } -static void restack_win(session_t *ps, win *w, xcb_window_t new_above) { - xcb_window_t old_above; +static bool initialize_blur(session_t *ps) { + struct kernel_blur_args kargs; + struct gaussian_blur_args gargs; + struct box_blur_args bargs; - if (w->next) { - old_above = w->next->id; - } else { - old_above = XCB_NONE; + void *args = NULL; + switch (ps->o.blur_method) { + case BLUR_METHOD_BOX: + bargs.size = ps->o.blur_radius; + args = (void *)&bargs; + break; + case BLUR_METHOD_KERNEL: + kargs.kernel_count = ps->o.blur_kernel_count; + kargs.kernels = ps->o.blur_kerns; + args = (void *)&kargs; + break; + case BLUR_METHOD_GAUSSIAN: + gargs.size = ps->o.blur_radius; + gargs.deviation = ps->o.blur_deviation; + args = (void *)&gargs; + break; + default: return true; } - log_debug("Restack %#010x (%s), old_above: %#010x, new_above: %#010x", w->id, - w->name, old_above, new_above); - - if (old_above != new_above) { - w->reg_ignore_valid = false; - rc_region_unref(&w->reg_ignore); - if (w->next) { - w->next->reg_ignore_valid = false; - rc_region_unref(&w->next->reg_ignore); - } - win **prev = NULL, **prev_old = NULL; + ps->backend_blur_context = ps->backend_data->ops->create_blur_context( + ps->backend_data, ps->o.blur_method, args); + return ps->backend_blur_context != NULL; +} - bool found = false; - for (prev = &ps->list; *prev; prev = &(*prev)->next) { - if ((*prev)->id == new_above && (*prev)->state != WSTATE_DESTROYING) { - found = true; - break; - } +/// Init the backend and bind all the window pixmap to backend images +static bool initialize_backend(session_t *ps) { + if (ps->o.experimental_backends) { + assert(!ps->backend_data); + // Reinitialize win_data + ps->backend_data = backend_list[ps->o.backend]->init(ps); + if (!ps->backend_data) { + log_fatal("Failed to initialize backend, aborting..."); + quit_compton(ps); + return false; } + ps->backend_data->ops = backend_list[ps->o.backend]; - if (new_above && !found) { - log_error("(%#010x, %#010x): Failed to found new above window.", - w->id, new_above); - return; + if (!initialize_blur(ps)) { + log_fatal("Failed to prepare for background blur, aborting..."); + quit_compton(ps); + return false; } - for (prev_old = &ps->list; *prev_old; prev_old = &(*prev_old)->next) { - if ((*prev_old) == w) { - break; + // window_stack shouldn't include window that's + // not in the hash table at this point. Since + // there cannot be any fading windows. + HASH_ITER2(ps->windows, _w) { + if (!_w->managed) { + continue; } - } - - *prev_old = w->next; - w->next = *prev; - *prev = w; - - // add damage for this window - add_damage_from_win(ps, w); - -#ifdef DEBUG_RESTACK - log_trace("Window stack modified. Current stack:"); - for (win *c = ps->list; c; c = c->next) { - const char *desc = ""; - if (c->state == WSTATE_DESTROYING) { - desc = "(D) "; + auto w = (struct managed_win *)_w; + if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { + if (!win_bind_image(ps, w)) { + w->flags |= WIN_FLAGS_IMAGE_ERROR; + } } - log_trace("%#010x \"%s\" %s", c->id, c->name, desc); } -#endif } + + // The old backends binds pixmap lazily, nothing to do here + return true; } /// Handle configure event of a root window void configure_root(session_t *ps, int width, int height) { - // On root window changes + log_info("Root configuration changed, new geometry: %dx%d", width, height); bool has_root_change = false; - if (ps->o.experimental_backends) { - has_root_change = ps->backend_data->ops->root_change != NULL; + if (ps->redirected) { + // On root window changes + if (ps->o.experimental_backends) { + assert(ps->backend_data); + has_root_change = ps->backend_data->ops->root_change != NULL; + } else { + // Old backend can handle root change + has_root_change = true; + } + if (!has_root_change) { - // deinit/reinit backend if the backend cannot handle root change - ps->backend_data->ops->deinit(ps->backend_data); - ps->backend_data = NULL; + // deinit/reinit backend and free up resources if the backend + // cannot handle root change + destroy_backend(ps); } - } else { free_paint(ps, &ps->tgt_buffer); } @@ -766,897 +775,176 @@ void configure_root(session_t *ps, int width, int height) { rebuild_screen_reg(ps); rebuild_shadow_exclude_reg(ps); - for (int i = 0; i < ps->ndamage; i++) { - pixman_region32_clear(&ps->damage_ring[i]); - } - ps->damage = ps->damage_ring + ps->ndamage - 1; // Invalidate reg_ignore from the top - rc_region_unref(&ps->list->reg_ignore); - ps->list->reg_ignore_valid = false; + auto top_w = win_stack_find_next_managed(ps, &ps->window_stack); + if (top_w) { + rc_region_unref(&top_w->reg_ignore); + top_w->reg_ignore_valid = false; + } + if (ps->redirected) { + for (int i = 0; i < ps->ndamage; i++) { + pixman_region32_clear(&ps->damage_ring[i]); + } + ps->damage = ps->damage_ring + ps->ndamage - 1; #ifdef CONFIG_OPENGL - // GLX root change callback - if (BKEND_GLX == ps->o.backend && !ps->o.experimental_backends) { - glx_on_root_change(ps); - } + // GLX root change callback + if (BKEND_GLX == ps->o.backend && !ps->o.experimental_backends) { + glx_on_root_change(ps); + } #endif - if (ps->o.experimental_backends) { if (has_root_change) { - ps->backend_data->ops->root_change(ps->backend_data, ps); + if (ps->backend_data != NULL) { + ps->backend_data->ops->root_change(ps->backend_data, ps); + } + // Old backend's root_change is not a specific function } else { - ps->backend_data = backend_list[ps->o.backend]->init(ps); - ps->backend_data->ops = backend_list[ps->o.backend]; - if (!ps->backend_data) { + if (!initialize_backend(ps)) { log_fatal("Failed to re-initialize backend after root " "change, aborting..."); ps->quit = true; // TODO only event handlers should request ev_break, // otherwise it's too hard to keep track of what can break // the event loop - ev_break(ps->loop, EVBREAK_ALL); - return; - } - } - } - force_repaint(ps); - return; -} - -/// Handle configure event of a regular window -void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { - win *w = find_win(ps, ce->window); - region_t damage; - pixman_region32_init(&damage); - - if (!w) { - return; - } - - if (w->state == WSTATE_UNMAPPED || w->state == WSTATE_UNMAPPING || - w->state == WSTATE_DESTROYING) { - /* save the configure event for when the window maps */ - w->need_configure = true; - w->queue_configure = *ce; - restack_win(ps, w, ce->above_sibling); - } else { - if (!w->need_configure) { - restack_win(ps, w, ce->above_sibling); - } - - bool factor_change = false; - w->need_configure = false; - win_extents(w, &damage); - - // If window geometry change, free old extents - if (w->g.x != ce->x || w->g.y != ce->y || w->g.width != ce->width || - w->g.height != ce->height || w->g.border_width != ce->border_width) - factor_change = true; - - w->g.x = ce->x; - w->g.y = ce->y; - - if (w->g.width != ce->width || w->g.height != ce->height || - w->g.border_width != ce->border_width) { - w->g.width = ce->width; - w->g.height = ce->height; - w->g.border_width = ce->border_width; - win_on_win_size_change(ps, w); - win_update_bounding_shape(ps, w); - } - - region_t new_extents; - pixman_region32_init(&new_extents); - win_extents(w, &new_extents); - pixman_region32_union(&damage, &damage, &new_extents); - pixman_region32_fini(&new_extents); - - if (factor_change) { - win_on_factor_change(ps, w); - add_damage(ps, &damage); - win_update_screen(ps, w); - } - } - - pixman_region32_fini(&damage); - - // override_redirect flag cannot be changed after window creation, as far - // as I know, so there's no point to re-match windows here. - w->a.override_redirect = ce->override_redirect; -} - -static void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce) { - win *w = find_win(ps, ce->window); - xcb_window_t new_above; - - if (!w) - return; - - if (ce->place == PlaceOnTop) { - new_above = ps->list->id; - } else { - new_above = XCB_NONE; - } - - restack_win(ps, w, new_above); -} - -static inline void root_damaged(session_t *ps) { - if (ps->root_tile_paint.pixmap) { - free_root_tile(ps); - } - - if (ps->o.experimental_backends) { - if (ps->root_image) { - ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); - } - auto pixmap = x_get_root_back_pixmap(ps); - if (pixmap != XCB_NONE) { - ps->root_image = ps->backend_data->ops->bind_pixmap( - ps->backend_data, pixmap, x_get_visual_info(ps->c, ps->vis), false); - ps->backend_data->ops->image_op( - ps->backend_data, IMAGE_OP_RESIZE_TILE, ps->root_image, NULL, - NULL, (int[]){ps->root_width, ps->root_height}); - } - } - - // Mark screen damaged - force_repaint(ps); -} - -/** - * Xlib error handler function. - */ -static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { - if (!should_ignore(ps_g, ev->serial)) - x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code); - return 0; -} - -/** - * XCB error handler function. - */ -void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) { - if (!should_ignore(ps, err->sequence)) - x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code); -} - -static void expose_root(session_t *ps, const rect_t *rects, int nrects) { - region_t region; - pixman_region32_init_rects(®ion, rects, nrects); - add_damage(ps, ®ion); -} -/** - * Force a full-screen repaint. - */ -void force_repaint(session_t *ps) { - assert(pixman_region32_not_empty(&ps->screen_reg)); - queue_redraw(ps); - add_damage(ps, &ps->screen_reg); -} - -#ifdef CONFIG_DBUS -/** @name DBus hooks - */ -///@{ - -/** - * Set w->shadow_force of a window. - */ -void win_set_shadow_force(session_t *ps, win *w, switch_t val) { - if (val != w->shadow_force) { - w->shadow_force = val; - win_determine_shadow(ps, w); - queue_redraw(ps); - } -} - -/** - * Set w->fade_force of a window. - * - * Doesn't affect fading already in progress - */ -void win_set_fade_force(session_t *ps, win *w, switch_t val) { - w->fade_force = val; -} - -/** - * Set w->focused_force of a window. - */ -void win_set_focused_force(session_t *ps, win *w, switch_t val) { - if (val != w->focused_force) { - w->focused_force = val; - win_update_focused(ps, w); - queue_redraw(ps); - } -} - -/** - * Set w->invert_color_force of a window. - */ -void win_set_invert_color_force(session_t *ps, win *w, switch_t val) { - if (val != w->invert_color_force) { - w->invert_color_force = val; - win_determine_invert_color(ps, w); - queue_redraw(ps); - } -} - -/** - * Enable focus tracking. - */ -void opts_init_track_focus(session_t *ps) { - // Already tracking focus - if (ps->o.track_focus) - return; - - ps->o.track_focus = true; - - if (!ps->o.use_ewmh_active_win) { - // Start listening to FocusChange events - for (win *w = ps->list; w; w = w->next) - if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) - xcb_change_window_attributes( - ps->c, w->id, XCB_CW_EVENT_MASK, - (const uint32_t[]){ - determine_evmask(ps, w->id, WIN_EVMODE_FRAME)}); - } - - // Recheck focus - recheck_focus(ps); -} - -/** - * Set no_fading_openclose option. - * - * Don't affect fading already in progress - */ -void opts_set_no_fading_openclose(session_t *ps, bool newval) { - ps->o.no_fading_openclose = newval; -} - -//!@} -#endif - -static inline int attr_unused ev_serial(xcb_generic_event_t *ev) { - return ev->full_sequence; -} - -static inline const char *attr_unused ev_name(session_t *ps, xcb_generic_event_t *ev) { - static char buf[128]; - switch (ev->response_type & 0x7f) { - CASESTRRET(FocusIn); - CASESTRRET(FocusOut); - CASESTRRET(CreateNotify); - CASESTRRET(ConfigureNotify); - CASESTRRET(DestroyNotify); - CASESTRRET(MapNotify); - CASESTRRET(UnmapNotify); - CASESTRRET(ReparentNotify); - CASESTRRET(CirculateNotify); - CASESTRRET(Expose); - CASESTRRET(PropertyNotify); - CASESTRRET(ClientMessage); - } - - if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) - return "Damage"; - - if (ps->shape_exists && ev->response_type == ps->shape_event) - return "ShapeNotify"; - - if (ps->xsync_exists) { - int o = ev->response_type - ps->xsync_event; - switch (o) { - CASESTRRET(XSyncCounterNotify); - CASESTRRET(XSyncAlarmNotify); - } - } - - sprintf(buf, "Event %d", ev->response_type); - - return buf; -} - -static inline xcb_window_t attr_unused ev_window(session_t *ps, xcb_generic_event_t *ev) { - switch (ev->response_type) { - case FocusIn: - case FocusOut: return ((xcb_focus_in_event_t *)ev)->event; - case CreateNotify: return ((xcb_create_notify_event_t *)ev)->window; - case ConfigureNotify: return ((xcb_configure_notify_event_t *)ev)->window; - case DestroyNotify: return ((xcb_destroy_notify_event_t *)ev)->window; - case MapNotify: return ((xcb_map_notify_event_t *)ev)->window; - case UnmapNotify: return ((xcb_unmap_notify_event_t *)ev)->window; - case ReparentNotify: return ((xcb_reparent_notify_event_t *)ev)->window; - case CirculateNotify: return ((xcb_circulate_notify_event_t *)ev)->window; - case Expose: return ((xcb_expose_event_t *)ev)->window; - case PropertyNotify: return ((xcb_property_notify_event_t *)ev)->window; - case ClientMessage: return ((xcb_client_message_event_t *)ev)->window; - default: - if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) { - return ((xcb_damage_notify_event_t *)ev)->drawable; - } - - if (ps->shape_exists && ev->response_type == ps->shape_event) { - return ((xcb_shape_notify_event_t *)ev)->affected_window; - } - - return 0; - } -} - -static inline const char *ev_focus_mode_name(xcb_focus_in_event_t *ev) { - switch (ev->mode) { - CASESTRRET(NotifyNormal); - CASESTRRET(NotifyWhileGrabbed); - CASESTRRET(NotifyGrab); - CASESTRRET(NotifyUngrab); - } - - return "Unknown"; -} - -static inline const char *ev_focus_detail_name(xcb_focus_in_event_t *ev) { - switch (ev->detail) { - CASESTRRET(NotifyAncestor); - CASESTRRET(NotifyVirtual); - CASESTRRET(NotifyInferior); - CASESTRRET(NotifyNonlinear); - CASESTRRET(NotifyNonlinearVirtual); - CASESTRRET(NotifyPointer); - CASESTRRET(NotifyPointerRoot); - CASESTRRET(NotifyDetailNone); - } - - return "Unknown"; -} - -static inline void attr_unused ev_focus_report(xcb_focus_in_event_t *ev) { - log_trace("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev), - ev_focus_detail_name(ev)); -} - -// === Events === - -/** - * Determine whether we should respond to a FocusIn/Out - * event. - */ -/* -inline static bool -ev_focus_accept(XFocusChangeEvent *ev) { - return NotifyNormal == ev->mode || NotifyUngrab == ev->mode; -} -*/ - -static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) { -#ifdef DEBUG_EVENTS - ev_focus_report(ev); -#endif - - recheck_focus(ps); -} - -inline static void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { -#ifdef DEBUG_EVENTS - ev_focus_report(ev); -#endif - - recheck_focus(ps); -} - -inline static void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { - assert(ev->parent == ps->root); - add_win(ps, ev->window, 0); -} - -inline static void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) { - log_trace("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }", - ev->event, ev->window, ev->above_sibling, ev->override_redirect); - if (ev->window == ps->root) { - configure_root(ps, ev->width, ev->height); - } else { - configure_win(ps, ev); - } -} - -inline static void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) { - win *w = find_win(ps, ev->window); - if (w) { - unmap_win(ps, &w, true); - } -} - -inline static void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { - map_win_by_id(ps, ev->window); - // FocusIn/Out may be ignored when the window is unmapped, so we must - // recheck focus here - if (ps->o.track_focus) { - recheck_focus(ps); - } -} - -inline static void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) { - win *w = find_win(ps, ev->window); - if (w) { - unmap_win(ps, &w, false); - } -} - -inline static void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) { - log_trace("{ new_parent: %#010x, override_redirect: %d }", ev->parent, - ev->override_redirect); - - if (ev->parent == ps->root) { - // new window - add_win(ps, ev->window, 0); - } else { - // otherwise, find and destroy the window first - win *w = find_win(ps, ev->window); - if (w) { - unmap_win(ps, &w, true); - } - - // Reset event mask in case something wrong happens - xcb_change_window_attributes( - ps->c, ev->window, XCB_CW_EVENT_MASK, - (const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)}); - - // Check if the window is an undetected client window - // Firstly, check if it's a known client window - if (!find_toplevel(ps, ev->window)) { - // If not, look for its frame window - win *w_top = find_toplevel2(ps, ev->parent); - // If found, and the client window has not been determined, or its - // frame may not have a correct client, continue - if (w_top && (!w_top->client_win || w_top->client_win == w_top->id)) { - // If it has WM_STATE, mark it the client window - if (wid_has_prop(ps, ev->window, ps->atom_client)) { - w_top->wmwin = false; - win_unmark_client(ps, w_top); - win_mark_client(ps, w_top, ev->window); - } - // Otherwise, watch for WM_STATE on it - else { - xcb_change_window_attributes( - ps->c, ev->window, XCB_CW_EVENT_MASK, - (const uint32_t[]){ - determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) | - XCB_EVENT_MASK_PROPERTY_CHANGE}); - } - } - } - } -} - -inline static void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) { - circulate_win(ps, ev); -} - -inline static void ev_expose(session_t *ps, xcb_expose_event_t *ev) { - if (ev->window == ps->root || (ps->overlay && ev->window == ps->overlay)) { - int more = ev->count + 1; - if (ps->n_expose == ps->size_expose) { - if (ps->expose_rects) { - ps->expose_rects = - crealloc(ps->expose_rects, ps->size_expose + more); - ps->size_expose += more; - } else { - ps->expose_rects = ccalloc(more, rect_t); - ps->size_expose = more; - } - } - - ps->expose_rects[ps->n_expose].x1 = ev->x; - ps->expose_rects[ps->n_expose].y1 = ev->y; - ps->expose_rects[ps->n_expose].x2 = ev->x + ev->width; - ps->expose_rects[ps->n_expose].y2 = ev->y + ev->height; - ps->n_expose++; - - if (ev->count == 0) { - expose_root(ps, ps->expose_rects, ps->n_expose); - ps->n_expose = 0; - } - } -} - -/** - * Update current active window based on EWMH _NET_ACTIVE_WIN. - * - * Does not change anything if we fail to get the attribute or the window - * returned could not be found. - */ -static void update_ewmh_active_win(session_t *ps) { - // Search for the window - xcb_window_t wid = wid_get_prop_window(ps, ps->root, ps->atom_ewmh_active_win); - win *w = find_win_all(ps, wid); - - // Mark the window focused. No need to unfocus the previous one. - if (w) - win_set_focused(ps, w, true); -} - -inline static void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) { -#ifdef DEBUG_EVENTS - { - // Print out changed atom - xcb_get_atom_name_reply_t *reply = - xcb_get_atom_name_reply(ps->c, xcb_get_atom_name(ps->c, ev->atom), NULL); - const char *name = "?"; - int name_len = 1; - if (reply) { - name = xcb_get_atom_name_name(reply); - name_len = xcb_get_atom_name_name_length(reply); - } - - log_trace("{ atom = %.*s }", name_len, name); - free(reply); - } -#endif - - if (ps->root == ev->window) { - if (ps->o.track_focus && ps->o.use_ewmh_active_win && - ps->atom_ewmh_active_win == ev->atom) { - update_ewmh_active_win(ps); - } else { - // Destroy the root "image" if the wallpaper probably changed - if (x_is_root_back_pixmap_atom(ps, ev->atom)) { - root_damaged(ps); - } - } - - // Unconcerned about any other proprties on root window - return; - } - - // If WM_STATE changes - if (ev->atom == ps->atom_client) { - // Check whether it could be a client window - if (!find_toplevel(ps, ev->window)) { - // Reset event mask anyway - xcb_change_window_attributes(ps->c, ev->window, XCB_CW_EVENT_MASK, - (const uint32_t[]){determine_evmask( - ps, ev->window, WIN_EVMODE_UNKNOWN)}); - - win *w_top = find_toplevel2(ps, ev->window); - // Initialize client_win as early as possible - if (w_top && (!w_top->client_win || w_top->client_win == w_top->id) && - wid_has_prop(ps, ev->window, ps->atom_client)) { - w_top->wmwin = false; - win_unmark_client(ps, w_top); - win_mark_client(ps, w_top, ev->window); - } - } - } - - // If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but - // there are always some stupid applications. (#144) - if (ev->atom == ps->atom_win_type) { - win *w = NULL; - if ((w = find_toplevel(ps, ev->window))) - win_update_wintype(ps, w); - } - - // If _NET_WM_OPACITY changes - if (ev->atom == ps->atom_opacity) { - win *w = find_win(ps, ev->window) ?: find_toplevel(ps, ev->window); - if (w) { - win_update_opacity_prop(ps, w); - // we cannot receive OPACITY change when window is destroyed - assert(w->state != WSTATE_DESTROYING); - if (w->state == WSTATE_MAPPED) { - // See the winstate_t transition table - w->state = WSTATE_FADING; + ev_break(ps->loop, EVBREAK_ALL); + return; } - w->opacity_tgt = win_calc_opacity_target(ps, w); - } - } - - // If frame extents property changes - if (ps->o.frame_opacity && ev->atom == ps->atom_frame_extents) { - win *w = find_toplevel(ps, ev->window); - if (w) { - win_update_frame_extents(ps, w, ev->window); - // If frame extents change, the window needs repaint - add_damage_from_win(ps, w); } + force_repaint(ps); } + return; +} - // If name changes - if (ps->o.track_wdata && (ps->atom_name == ev->atom || ps->atom_name_ewmh == ev->atom)) { - win *w = find_toplevel(ps, ev->window); - if (w && 1 == win_get_name(ps, w)) { - win_on_factor_change(ps, w); - } +void root_damaged(session_t *ps) { + if (ps->root_tile_paint.pixmap) { + free_root_tile(ps); } - // If class changes - if (ps->o.track_wdata && ps->atom_class == ev->atom) { - win *w = find_toplevel(ps, ev->window); - if (w) { - win_get_class(ps, w); - win_on_factor_change(ps, w); - } + if (!ps->redirected) { + return; } - // If role changes - if (ps->o.track_wdata && ps->atom_role == ev->atom) { - win *w = find_toplevel(ps, ev->window); - if (w && 1 == win_get_role(ps, w)) { - win_on_factor_change(ps, w); + if (ps->backend_data) { + if (ps->root_image) { + ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); } - } - - // If _COMPTON_SHADOW changes - if (ps->o.respect_prop_shadow && ps->atom_compton_shadow == ev->atom) { - win *w = find_win(ps, ev->window); - if (w) - win_update_prop_shadow(ps, w); - } - - // If a leader property changes - if ((ps->o.detect_transient && ps->atom_transient == ev->atom) || - (ps->o.detect_client_leader && ps->atom_client_leader == ev->atom)) { - win *w = find_toplevel(ps, ev->window); - if (w) { - win_update_leader(ps, w); + auto pixmap = x_get_root_back_pixmap(ps); + if (pixmap != XCB_NONE) { + ps->root_image = ps->backend_data->ops->bind_pixmap( + ps->backend_data, pixmap, x_get_visual_info(ps->c, ps->vis), false); + ps->backend_data->ops->image_op( + ps->backend_data, IMAGE_OP_RESIZE_TILE, ps->root_image, NULL, + NULL, (int[]){ps->root_width, ps->root_height}); } } - // Check for other atoms we are tracking - for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) { - if (platom->atom == ev->atom) { - win *w = find_win(ps, ev->window); - if (!w) - w = find_toplevel(ps, ev->window); - if (w) - win_on_factor_change(ps, w); - break; - } - } + // Mark screen damaged + force_repaint(ps); } -inline static void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de) { - /* - if (ps->root == de->drawable) { - root_damaged(); - return; - } */ - - win *w = find_win(ps, de->drawable); - - if (!w) - return; - - repair_win(ps, w); +/** + * Xlib error handler function. + */ +static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { + if (!should_ignore(ps_g, ev->serial)) + x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code); + return 0; } -inline static void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) { - win *w = find_win(ps, ev->affected_window); - if (!w || w->a.map_state == XCB_MAP_STATE_UNMAPPED) - return; - - /* - * Empty bounding_shape may indicated an - * unmapped/destroyed window, in which case - * seemingly BadRegion errors would be triggered - * if we attempt to rebuild border_size - */ - // Mark the old border_size as damaged - region_t tmp = win_get_bounding_shape_global_by_val(w); - add_damage(ps, &tmp); - pixman_region32_fini(&tmp); - - win_update_bounding_shape(ps, w); - - // Mark the new border_size as damaged - tmp = win_get_bounding_shape_global_by_val(w); - add_damage(ps, &tmp); - pixman_region32_fini(&tmp); - - w->reg_ignore_valid = false; +/** + * XCB error handler function. + */ +void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) { + if (!should_ignore(ps, err->sequence)) + x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code); } /** - * Handle ScreenChangeNotify events from X RandR extension. + * Force a full-screen repaint. */ -static void ev_screen_change_notify(session_t *ps, - xcb_randr_screen_change_notify_event_t attr_unused *ev) { - if (ps->o.xinerama_shadow_crop) - cxinerama_upd_scrs(ps); - - if (ps->o.sw_opti && !ps->o.refresh_rate) { - update_refresh_rate(ps); - if (!ps->refresh_rate) { - log_warn("Refresh rate detection failed. swopti will be " - "temporarily disabled"); - } - } +void force_repaint(session_t *ps) { + assert(pixman_region32_not_empty(&ps->screen_reg)); + queue_redraw(ps); + add_damage(ps, &ps->screen_reg); } -inline static void -ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) { - // The only selection we own is the _NET_WM_CM_Sn selection. - // If we lose that one, we should exit. - log_fatal("Another composite manager started and took the _NET_WM_CM_Sn " - "selection."); - exit(1); -} +#ifdef CONFIG_DBUS +/** @name DBus hooks + */ +///@{ /** - * Get a window's name from window ID. + * Set no_fading_openclose option. + * + * Don't affect fading already in progress */ -static inline void attr_unused ev_window_name(session_t *ps, xcb_window_t wid, char **name) { - *name = ""; - if (wid) { - *name = "(Failed to get title)"; - if (ps->root == wid) - *name = "(Root window)"; - else if (ps->overlay == wid) - *name = "(Overlay)"; - else { - win *w = find_win(ps, wid); - if (!w) - w = find_toplevel(ps, wid); - - if (w) - win_get_name(ps, w); - if (w && w->name) - *name = w->name; - else - *name = "unknown"; - } - } +void opts_set_no_fading_openclose(session_t *ps, bool newval) { + ps->o.no_fading_openclose = newval; } -static void ev_handle(session_t *ps, xcb_generic_event_t *ev) { - if ((ev->response_type & 0x7f) != KeymapNotify) { - discard_ignore(ps, ev->full_sequence); - } - -#ifdef DEBUG_EVENTS - if (ev->response_type != ps->damage_event + XCB_DAMAGE_NOTIFY) { - xcb_window_t wid = ev_window(ps, ev); - char *window_name = NULL; - ev_window_name(ps, wid, &window_name); - - log_trace("event %10.10s serial %#010x window %#010lx \"%s\"", - ev_name(ps, ev), ev_serial(ev), wid, window_name); - } +//!@} #endif - // Check if a custom XEvent constructor was registered in xlib for this event - // type, and call it discarding the constructed XEvent if any. XESetWireToEvent - // might be used by libraries to intercept messages from the X server e.g. the - // OpenGL lib waiting for DRI2 events. - - // XXX This exists to workaround compton issue #33, #34, #47 - // For even more details, see: - // https://bugs.freedesktop.org/show_bug.cgi?id=35945 - // https://lists.freedesktop.org/archives/xcb/2011-November/007337.html - auto proc = XESetWireToEvent(ps->dpy, ev->response_type, 0); - if (proc) { - XESetWireToEvent(ps->dpy, ev->response_type, proc); - XEvent dummy; - - // Stop Xlib from complaining about lost sequence numbers. - // proc might also just be Xlib internal event processing functions, and - // because they probably won't see all X replies, they will complain about - // missing sequence numbers. - // - // We only need the low 16 bits - ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff); - proc(ps->dpy, &dummy, (xEvent *)ev); - } - - // XXX redraw needs to be more fine grained - queue_redraw(ps); - - switch (ev->response_type) { - case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break; - case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break; - case CreateNotify: ev_create_notify(ps, (xcb_create_notify_event_t *)ev); break; - case ConfigureNotify: - ev_configure_notify(ps, (xcb_configure_notify_event_t *)ev); - break; - case DestroyNotify: - ev_destroy_notify(ps, (xcb_destroy_notify_event_t *)ev); - break; - case MapNotify: ev_map_notify(ps, (xcb_map_notify_event_t *)ev); break; - case UnmapNotify: ev_unmap_notify(ps, (xcb_unmap_notify_event_t *)ev); break; - case ReparentNotify: - ev_reparent_notify(ps, (xcb_reparent_notify_event_t *)ev); - break; - case CirculateNotify: - ev_circulate_notify(ps, (xcb_circulate_notify_event_t *)ev); - break; - case Expose: ev_expose(ps, (xcb_expose_event_t *)ev); break; - case PropertyNotify: - ev_property_notify(ps, (xcb_property_notify_event_t *)ev); - break; - case SelectionClear: - ev_selection_clear(ps, (xcb_selection_clear_event_t *)ev); - break; - case 0: ev_xcb_error(ps, (xcb_generic_error_t *)ev); break; - default: - if (ps->shape_exists && ev->response_type == ps->shape_event) { - ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev); - break; - } - if (ps->randr_exists && - ev->response_type == (ps->randr_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY)) { - ev_screen_change_notify( - ps, (xcb_randr_screen_change_notify_event_t *)ev); - break; - } - if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) { - ev_damage_notify(ps, (xcb_damage_notify_event_t *)ev); - break; - } - } -} - -// === Main === - /** * Register a window as symbol, and initialize GLX context if wanted. */ static bool register_cm(session_t *ps) { assert(!ps->reg_win); - ps->reg_win = - XCreateSimpleWindow(ps->dpy, ps->root, 0, 0, 1, 1, 0, XCB_NONE, XCB_NONE); + ps->reg_win = x_new_id(ps->c); + auto e = xcb_request_check( + ps->c, xcb_create_window_checked(ps->c, XCB_COPY_FROM_PARENT, ps->reg_win, ps->root, + 0, 0, 1, 1, 0, XCB_NONE, ps->vis, 0, NULL)); - if (!ps->reg_win) { + if (e) { log_fatal("Failed to create window."); + free(e); return false; } // Unredirect the window if it's redirected, just in case if (ps->redirected) xcb_composite_unredirect_window(ps->c, ps->reg_win, - XCB_COMPOSITE_REDIRECT_MANUAL); + ps->o.debug_mode + ? XCB_COMPOSITE_REDIRECT_AUTOMATIC + : XCB_COMPOSITE_REDIRECT_MANUAL); { XClassHint *h = XAllocClassHint(); if (h) { h->res_name = "compton"; - h->res_class = "xcompmgr"; + h->res_class = "compton"; } - Xutf8SetWMProperties(ps->dpy, ps->reg_win, "xcompmgr", "xcompmgr", NULL, - 0, NULL, NULL, h); - cxfree(h); + Xutf8SetWMProperties(ps->dpy, ps->reg_win, "compton", "compton", NULL, 0, + NULL, NULL, h); + XFree(h); } // Set _NET_WM_PID { - uint32_t pid = getpid(); + auto pid = getpid(); xcb_change_property(ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, - get_atom(ps, "_NET_WM_PID"), XCB_ATOM_CARDINAL, 32, 1, - &pid); + ps->atoms->a_NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid); } // Set COMPTON_VERSION - if (!wid_set_text_prop(ps, ps->reg_win, get_atom(ps, "COMPTON_VERSION"), + if (!wid_set_text_prop(ps, ps->reg_win, get_atom(ps->atoms, "COMPTON_VERSION"), COMPTON_VERSION)) { log_error("Failed to set COMPTON_VERSION."); } // Acquire X Selection _NET_WM_CM_S? if (!ps->o.no_x_selection) { - unsigned len = strlen(REGISTER_PROP) + 2; - int s = ps->scr; - Atom atom; + const char register_prop[] = "_NET_WM_CM_S"; + xcb_atom_t atom; - while (s >= 10) { - ++len; - s /= 10; + char *buf = NULL; + if (asprintf(&buf, "%s%d", register_prop, ps->scr) < 0) { + log_fatal("Failed to allocate memory"); + return false; } - - auto buf = ccalloc(len, char); - snprintf(buf, len, REGISTER_PROP "%d", ps->scr); - buf[len - 1] = '\0'; - atom = get_atom(ps, buf); + atom = get_atom(ps->atoms, buf); free(buf); xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply( @@ -1693,47 +981,10 @@ static inline bool write_pid(session_t *ps) { return true; } -/** - * Fetch all required atoms and save them to a session. - */ -static void init_atoms(session_t *ps) { - ps->atom_opacity = get_atom(ps, "_NET_WM_WINDOW_OPACITY"); - ps->atom_frame_extents = get_atom(ps, "_NET_FRAME_EXTENTS"); - ps->atom_client = get_atom(ps, "WM_STATE"); - ps->atom_name = XCB_ATOM_WM_NAME; - ps->atom_name_ewmh = get_atom(ps, "_NET_WM_NAME"); - ps->atom_class = XCB_ATOM_WM_CLASS; - ps->atom_role = get_atom(ps, "WM_WINDOW_ROLE"); - ps->atom_transient = XCB_ATOM_WM_TRANSIENT_FOR; - ps->atom_client_leader = get_atom(ps, "WM_CLIENT_LEADER"); - ps->atom_ewmh_active_win = get_atom(ps, "_NET_ACTIVE_WINDOW"); - ps->atom_compton_shadow = get_atom(ps, "_COMPTON_SHADOW"); - - ps->atom_win_type = get_atom(ps, "_NET_WM_WINDOW_TYPE"); - ps->atoms_wintypes[WINTYPE_UNKNOWN] = 0; - ps->atoms_wintypes[WINTYPE_DESKTOP] = get_atom(ps, "_NET_WM_WINDOW_TYPE_DESKTOP"); - ps->atoms_wintypes[WINTYPE_DOCK] = get_atom(ps, "_NET_WM_WINDOW_TYPE_DOCK"); - ps->atoms_wintypes[WINTYPE_TOOLBAR] = get_atom(ps, "_NET_WM_WINDOW_TYPE_TOOLBAR"); - ps->atoms_wintypes[WINTYPE_MENU] = get_atom(ps, "_NET_WM_WINDOW_TYPE_MENU"); - ps->atoms_wintypes[WINTYPE_UTILITY] = get_atom(ps, "_NET_WM_WINDOW_TYPE_UTILITY"); - ps->atoms_wintypes[WINTYPE_SPLASH] = get_atom(ps, "_NET_WM_WINDOW_TYPE_SPLASH"); - ps->atoms_wintypes[WINTYPE_DIALOG] = get_atom(ps, "_NET_WM_WINDOW_TYPE_DIALOG"); - ps->atoms_wintypes[WINTYPE_NORMAL] = get_atom(ps, "_NET_WM_WINDOW_TYPE_NORMAL"); - ps->atoms_wintypes[WINTYPE_DROPDOWN_MENU] = - get_atom(ps, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); - ps->atoms_wintypes[WINTYPE_POPUP_MENU] = - get_atom(ps, "_NET_WM_WINDOW_TYPE_POPUP_MENU"); - ps->atoms_wintypes[WINTYPE_TOOLTIP] = get_atom(ps, "_NET_WM_WINDOW_TYPE_TOOLTIP"); - ps->atoms_wintypes[WINTYPE_NOTIFY] = - get_atom(ps, "_NET_WM_WINDOW_TYPE_NOTIFICATION"); - ps->atoms_wintypes[WINTYPE_COMBO] = get_atom(ps, "_NET_WM_WINDOW_TYPE_COMBO"); - ps->atoms_wintypes[WINTYPE_DND] = get_atom(ps, "_NET_WM_WINDOW_TYPE_DND"); -} - /** * Update refresh rate info with X Randr extension. */ -static void update_refresh_rate(session_t *ps) { +void update_refresh_rate(session_t *ps) { xcb_randr_get_screen_info_reply_t *randr_info = xcb_randr_get_screen_info_reply( ps->c, xcb_randr_get_screen_info(ps->c, ps->root), NULL); @@ -1798,7 +1049,7 @@ static double swopti_handle_timeout(session_t *ps) { return 0; // Add an offset so we wait until the next refresh after timeout - return (ps->refresh_intv - offset) / 1e6; + return (double)(ps->refresh_intv - offset) / 1e6; } /** @@ -1817,20 +1068,17 @@ static bool init_overlay(session_t *ps) { if (ps->overlay) { // Set window region of the overlay window, code stolen from // compiz-0.8.8 - xcb_generic_error_t *e; - e = XCB_SYNCED_VOID(xcb_shape_mask, ps->c, XCB_SHAPE_SO_SET, - XCB_SHAPE_SK_BOUNDING, ps->overlay, 0, 0, 0); - if (e) { + if (!XCB_AWAIT_VOID(xcb_shape_mask, ps->c, XCB_SHAPE_SO_SET, + XCB_SHAPE_SK_BOUNDING, ps->overlay, 0, 0, 0)) { log_fatal("Failed to set the bounding shape of overlay, giving " "up."); - exit(1); + return false; } - e = XCB_SYNCED_VOID(xcb_shape_rectangles, ps->c, XCB_SHAPE_SO_SET, + if (!XCB_AWAIT_VOID(xcb_shape_rectangles, ps->c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, - ps->overlay, 0, 0, 0, NULL); - if (e) { + ps->overlay, 0, 0, 0, NULL)) { log_fatal("Failed to set the input shape of overlay, giving up."); - exit(1); + return false; } // Listen to Expose events on the overlay @@ -1841,17 +1089,54 @@ static bool init_overlay(session_t *ps) { // overlay // root_damage = XDamageCreate(ps->dpy, root, XDamageReportNonEmpty); - // Unmap overlay, firstly. But this typically does not work because - // the window isn't created yet. - // xcb_unmap_window(c, ps->overlay); - // XFlush(ps->dpy); + // Unmap the overlay, we will map it when needed in redir_start + XCB_AWAIT_VOID(xcb_unmap_window, ps->c, ps->overlay); } else { log_error("Cannot get X Composite overlay window. Falling " "back to painting on root window."); } log_debug("overlay = %#010x", ps->overlay); - return ps->overlay; + return true; +} + +static bool init_debug_window(session_t *ps) { + xcb_colormap_t colormap = x_new_id(ps->c); + ps->debug_window = x_new_id(ps->c); + + auto err = xcb_request_check( + ps->c, xcb_create_colormap_checked(ps->c, XCB_COLORMAP_ALLOC_NONE, colormap, + ps->root, ps->vis)); + if (err) { + goto err_out; + } + + err = xcb_request_check( + ps->c, xcb_create_window_checked(ps->c, (uint8_t)ps->depth, ps->debug_window, + ps->root, 0, 0, to_u16_checked(ps->root_width), + to_u16_checked(ps->root_height), 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, ps->vis, + XCB_CW_COLORMAP, (uint32_t[]){colormap, 0})); + if (err) { + goto err_out; + } + + err = xcb_request_check(ps->c, xcb_map_window(ps->c, ps->debug_window)); + if (err) { + goto err_out; + } + return true; + +err_out: + free(err); + return false; +} + +xcb_window_t session_get_target_window(session_t *ps) { + if (ps->o.debug_mode) { + return ps->debug_window; + } + return ps->overlay != XCB_NONE ? ps->overlay : ps->root; } /** @@ -1860,80 +1145,51 @@ static bool init_overlay(session_t *ps) { * @return whether the operation succeeded or not */ static bool redir_start(session_t *ps) { - if (!ps->redirected) { - log_debug("Screen redirected."); - - // Map overlay window. Done firstly according to this: - // https://bugzilla.gnome.org/show_bug.cgi?id=597014 - if (ps->overlay) { - xcb_map_window(ps->c, ps->overlay); - } + assert(!ps->redirected); + log_debug("Redirecting the screen."); - xcb_composite_redirect_subwindows(ps->c, ps->root, - XCB_COMPOSITE_REDIRECT_MANUAL); + // Map overlay window. Done firstly according to this: + // https://bugzilla.gnome.org/show_bug.cgi?id=597014 + if (ps->overlay) { + xcb_map_window(ps->c, ps->overlay); + } - x_sync(ps->c); + xcb_composite_redirect_subwindows(ps->c, ps->root, + ps->o.debug_mode ? XCB_COMPOSITE_REDIRECT_AUTOMATIC + : XCB_COMPOSITE_REDIRECT_MANUAL); - if (ps->o.experimental_backends) { - // Reinitialize win_data - ps->backend_data = backend_list[ps->o.backend]->init(ps); - ps->backend_data->ops = backend_list[ps->o.backend]; - if (!ps->backend_data) { - log_fatal("Failed to initialize backend, aborting..."); - ps->quit = true; - ev_break(ps->loop, EVBREAK_ALL); - return false; - } + x_sync(ps->c); - for (win *w = ps->list; w; w = w->next) { - if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { - auto pixmap = xcb_generate_id(ps->c); - xcb_composite_name_window_pixmap(ps->c, w->id, pixmap); - w->win_image = ps->backend_data->ops->bind_pixmap( - ps->backend_data, pixmap, - x_get_visual_info(ps->c, w->a.visual), true); - if (w->shadow) { - w->shadow_image = ps->backend_data->ops->render_shadow( - ps->backend_data, w->widthb, w->heightb, - ps->gaussian_map, ps->o.shadow_red, - ps->o.shadow_green, ps->o.shadow_blue, - ps->o.shadow_opacity); - } - } - } - } + if (!initialize_backend(ps)) { + return false; + } - if (ps->o.experimental_backends) { - ps->ndamage = ps->backend_data->ops->max_buffer_age; - } else { - ps->ndamage = maximum_buffer_age(ps); - } - ps->damage_ring = ccalloc(ps->ndamage, region_t); - ps->damage = ps->damage_ring + ps->ndamage - 1; + if (ps->o.experimental_backends) { + assert(ps->backend_data); + ps->ndamage = ps->backend_data->ops->max_buffer_age; + } else { + ps->ndamage = maximum_buffer_age(ps); + } + ps->damage_ring = ccalloc(ps->ndamage, region_t); + ps->damage = ps->damage_ring + ps->ndamage - 1; - for (int i = 0; i < ps->ndamage; i++) { - pixman_region32_init(&ps->damage_ring[i]); - } + for (int i = 0; i < ps->ndamage; i++) { + pixman_region32_init(&ps->damage_ring[i]); + } - /* - // Unredirect GL context window as this may have an effect on VSync: - // < http://dri.freedesktop.org/wiki/CompositeSwap > - xcb_composite_unredirect_window(c, ps->reg_win, - XCB_COMPOSITE_REDIRECT_MANUAL); if (ps->o.paint_on_overlay && ps->overlay) - { xcb_composite_unredirect_window(c, ps->overlay, - XCB_COMPOSITE_REDIRECT_MANUAL); - } */ + // Must call XSync() here + x_sync(ps->c); - // Must call XSync() here - x_sync(ps->c); + ps->redirected = true; - ps->redirected = true; + // Re-detect driver since we now have a backend + ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root); - root_damaged(ps); + root_damaged(ps); - // Repaint the whole screen - force_repaint(ps); - } + // Repaint the whole screen + force_repaint(ps); + log_debug("Screen redirected."); return true; } @@ -1941,69 +1197,31 @@ static bool redir_start(session_t *ps) { * Unredirect all windows. */ static void redir_stop(session_t *ps) { - if (ps->redirected) { - log_debug("Screen unredirected."); - // Destroy all Pictures as they expire once windows are unredirected - // If we don't destroy them here, looks like the resources are just - // kept inaccessible somehow - for (win *w = ps->list, *next; w; w = next) { - next = w->next; - // Wrapping up fading in progress - win_skip_fading(ps, &w); - - // `w` might be freed by win_check_fade_finished - if (!w) { - continue; - } - if (ps->o.experimental_backends) { - if (w->state == WSTATE_MAPPED) { - ps->backend_data->ops->release_image( - ps->backend_data, w->win_image); - if (w->shadow_image) { - ps->backend_data->ops->release_image( - ps->backend_data, w->shadow_image); - } - w->win_image = NULL; - w->shadow_image = NULL; - } else { - assert(!w->win_image); - assert(!w->shadow_image); - } - if (ps->root_image) { - ps->backend_data->ops->release_image( - ps->backend_data, ps->root_image); - ps->root_image = NULL; - } - } else { - free_paint(ps, &w->paint); - } - } + assert(ps->redirected); + log_debug("Unredirecting the screen."); - xcb_composite_unredirect_subwindows(ps->c, ps->root, - XCB_COMPOSITE_REDIRECT_MANUAL); - // Unmap overlay window - if (ps->overlay) - xcb_unmap_window(ps->c, ps->overlay); + destroy_backend(ps); - if (ps->o.experimental_backends) { - // deinit backend - ps->backend_data->ops->deinit(ps->backend_data); - ps->backend_data = NULL; - } + xcb_composite_unredirect_subwindows(ps->c, ps->root, + ps->o.debug_mode ? XCB_COMPOSITE_REDIRECT_AUTOMATIC + : XCB_COMPOSITE_REDIRECT_MANUAL); + // Unmap overlay window + if (ps->overlay) + xcb_unmap_window(ps->c, ps->overlay); - // Free the damage ring - for (int i = 0; i < ps->ndamage; ++i) { - pixman_region32_fini(&ps->damage_ring[i]); - } - ps->ndamage = 0; - free(ps->damage_ring); - ps->damage_ring = ps->damage = NULL; + // Free the damage ring + for (int i = 0; i < ps->ndamage; ++i) { + pixman_region32_fini(&ps->damage_ring[i]); + } + ps->ndamage = 0; + free(ps->damage_ring); + ps->damage_ring = ps->damage = NULL; - // Must call XSync() here - x_sync(ps->c); + // Must call XSync() here + x_sync(ps->c); - ps->redirected = false; - } + ps->redirected = false; + log_debug("Screen unredirected."); } // Handle queued events before we go to sleep @@ -2028,6 +1246,45 @@ static void handle_queued_x_events(EV_P_ ev_prepare *w, int revents) { } } +static void handle_new_windows(session_t *ps) { + list_foreach_safe(struct win, w, &ps->window_stack, stack_neighbour) { + if (w->is_new) { + auto new_w = fill_win(ps, w); + if (!new_w->managed) { + continue; + } + auto mw = (struct managed_win *)new_w; + if (mw->a.map_state == XCB_MAP_STATE_VIEWABLE) { + map_win(ps, mw); + + // This window might be damaged before we called fill_win + // and created the damage handle. And there is not way for + // us to find out. So just blindly mark it damaged + mw->ever_damaged = true; + add_damage_from_win(ps, mw); + } + } + } +} + +static void refresh_stale_images(session_t *ps) { + win_stack_foreach_managed(w, &ps->window_stack) { + if ((w->flags & WIN_FLAGS_IMAGE_STALE) != 0 && + (w->flags & WIN_FLAGS_IMAGE_ERROR) == 0) { + // Image needs to be updated, update it. + w->flags &= ~WIN_FLAGS_IMAGE_STALE; + if (w->state != WSTATE_UNMAPPING && + w->state != WSTATE_DESTROYING && ps->backend_data) { + // Rebind image only when the window does have an image + // available + if (!win_try_rebind_image(ps, w)) { + w->flags |= WIN_FLAGS_IMAGE_ERROR; + } + } + } + } +} + /** * Unredirection timeout callback. */ @@ -2043,27 +1300,81 @@ static void fade_timer_callback(EV_P_ ev_timer *w, int revents) { } static void _draw_callback(EV_P_ session_t *ps, int revents) { + if (ps->pending_updates) { + log_debug("Delayed handling of events, entering critical section"); + auto e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); + if (e) { + log_fatal("failed to grab x server"); + x_print_error(e->full_sequence, e->major_code, e->minor_code, + e->error_code); + return quit_compton(ps); + } + + // Catching up with X server + handle_queued_x_events(ps->loop, &ps->event_check, 0); + + // Call fill_win on new windows + handle_new_windows(ps); + + auto r = xcb_get_input_focus_reply(ps->c, xcb_get_input_focus(ps->c), NULL); + if (!ps->active_win || (r && r->focus != ps->active_win->base.id)) { + recheck_focus(ps); + } + + // Refresh pixmaps + refresh_stale_images(ps); + + e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); + if (e) { + log_fatal("failed to ungrab x server"); + x_print_error(e->full_sequence, e->major_code, e->minor_code, + e->error_code); + return quit_compton(ps); + } + + ps->pending_updates = false; + log_debug("Exiting critical section"); + } + if (ps->o.benchmark) { if (ps->o.benchmark_wid) { - win *wi = find_win(ps, ps->o.benchmark_wid); - if (!wi) { + auto w = find_managed_win(ps, ps->o.benchmark_wid); + if (!w) { log_fatal("Couldn't find specified benchmark window."); exit(1); } - add_damage_from_win(ps, wi); + add_damage_from_win(ps, w); } else { force_repaint(ps); } } + // TODO xcb_grab_server + // TODO clean up event queue + + handle_root_flags(ps); + + // TODO have a stripped down version of paint_preprocess that is used when screen + // is not redirected. its sole purpose should be to decide whether the screen + // should be redirected. bool fade_running = false; - win *t = paint_preprocess(ps, &fade_running); + bool was_redirected = ps->redirected; + auto bottom = paint_preprocess(ps, &fade_running); ps->tmout_unredir_hit = false; + if (!was_redirected && ps->redirected) { + // paint_preprocess redirected the screen, which might change the state of + // some of the windows (e.g. the window image might fail to bind, and the + // window would be put into an error state). so we rerun paint_preprocess + // here to make sure the rendering decision we make is up-to-date + log_debug("Re-run paint_preprocess"); + bottom = paint_preprocess(ps, &fade_running); + } + // Start/stop fade timer depends on whether window are fading - if (!fade_running && ev_is_active(&ps->fade_timer)) + if (!fade_running && ev_is_active(&ps->fade_timer)) { ev_timer_stop(ps->loop, &ps->fade_timer); - else if (fade_running && !ev_is_active(&ps->fade_timer)) { + } else if (fade_running && !ev_is_active(&ps->fade_timer)) { ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0); ev_timer_start(ps->loop, &ps->fade_timer); } @@ -2072,9 +1383,9 @@ static void _draw_callback(EV_P_ session_t *ps, int revents) { if (ps->redirected && ps->o.stoppaint_force != ON) { static int paint = 0; if (ps->o.experimental_backends) { - paint_all_new(ps, t, false); + paint_all_new(ps, bottom, false); } else { - paint_all(ps, t, false); + paint_all(ps, bottom, false); } paint++; @@ -2085,6 +1396,8 @@ static void _draw_callback(EV_P_ session_t *ps, int revents) { if (!fade_running) ps->fade_time = 0L; + // TODO xcb_ungrab_server + ps->redraw_needed = false; } @@ -2162,8 +1475,7 @@ static void reset_enable(EV_P_ ev_signal *w, int revents) { static void exit_enable(EV_P_ ev_signal *w, int revents) { session_t *ps = session_ptr(w, int_signal); log_info("compton is quitting..."); - ps->quit = true; - ev_break(ps->loop, EVBREAK_ALL); + quit_compton(ps); } /** @@ -2198,72 +1510,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, #ifdef CONFIG_OPENGL .glx_prog_win = GLX_PROG_MAIN_INIT, #endif - .o = - { - .backend = BKEND_XRENDER, - .glx_no_stencil = false, - .mark_wmwin_focused = false, - .mark_ovredir_focused = false, - .detect_rounded_corners = false, - .resize_damage = 0, - .unredir_if_possible = false, - .unredir_if_possible_blacklist = NULL, - .unredir_if_possible_delay = 0, - .redirected_force = UNSET, - .stoppaint_force = UNSET, - .dbus = false, - .benchmark = 0, - .benchmark_wid = XCB_NONE, - .logpath = NULL, - - .refresh_rate = 0, - .sw_opti = false, - - .shadow_red = 0.0, - .shadow_green = 0.0, - .shadow_blue = 0.0, - .shadow_radius = 18, - .shadow_offset_x = -15, - .shadow_offset_y = -15, - .shadow_opacity = .75, - .shadow_blacklist = NULL, - .shadow_ignore_shaped = false, - .respect_prop_shadow = false, - .xinerama_shadow_crop = false, - - .fade_in_step = 0.028, - .fade_out_step = 0.03, - .fade_delta = 10, - .no_fading_openclose = false, - .no_fading_destroyed_argb = false, - .fade_blacklist = NULL, - - .inactive_opacity = 1.0, - .inactive_opacity_override = false, - .active_opacity = 1.0, - .frame_opacity = 1.0, - .detect_client_opacity = false, - - .blur_background = false, - .blur_background_frame = false, - .blur_background_fixed = false, - .blur_background_blacklist = NULL, - .blur_kerns = {NULL}, - .inactive_dim = 0.0, - .inactive_dim_fixed = false, - .invert_color_list = NULL, - .opacity_rules = NULL, - - .use_ewmh_active_win = false, - .focus_blacklist = NULL, - .detect_transient = false, - .detect_client_leader = false, - - .track_focus = false, - .track_wdata = false, - .track_leader = false, - }, - .time_start = {0, 0}, .redirected = false, .alpha_picts = NULL, @@ -2276,7 +1522,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .size_expose = 0, .n_expose = 0, - .list = NULL, + .windows = NULL, .active_win = NULL, .active_leader = XCB_NONE, @@ -2315,17 +1561,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, #endif .xrfilter_convolution_exists = false, - .atom_opacity = XCB_NONE, - .atom_frame_extents = XCB_NONE, - .atom_client = XCB_NONE, - .atom_name = XCB_NONE, - .atom_name_ewmh = XCB_NONE, - .atom_class = XCB_NONE, - .atom_role = XCB_NONE, - .atom_transient = XCB_NONE, - .atom_ewmh_active_win = XCB_NONE, - .atom_compton_shadow = XCB_NONE, - .atom_win_type = XCB_NONE, .atoms_wintypes = {0}, .track_atom_lst = NULL, @@ -2334,7 +1569,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, #endif }; - log_init_tls(); auto stderr_logger = stderr_logger_new(); if (stderr_logger) { // stderr logger might fail to create if we are already @@ -2345,6 +1579,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // Allocate a session and copy default values into it session_t *ps = cmalloc(session_t); *ps = s_def; + list_init_head(&ps->window_stack); ps->loop = EV_DEFAULT; pixman_region32_init(&ps->screen_reg); @@ -2362,10 +1597,13 @@ static session_t *session_init(int argc, char **argv, Display *dpy, XSetErrorHandler(xerror); ps->scr = DefaultScreen(ps->dpy); - ps->root = RootWindow(ps->dpy, ps->scr); - ps->vis = XVisualIDFromVisual(DefaultVisual(ps->dpy, ps->scr)); - ps->depth = DefaultDepth(ps->dpy, ps->scr); + auto screen = x_screen_of_display(ps->c, ps->scr); + ps->vis = screen->root_visual; + ps->depth = screen->root_depth; + ps->root = screen->root; + ps->root_width = screen->width_in_pixels; + ps->root_height = screen->height_in_pixels; // Start listening to events on root earlier to catch all possible // root geometry changes @@ -2380,9 +1618,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, free(e); } - ps->root_width = DisplayWidth(ps->dpy, ps->scr); - ps->root_height = DisplayHeight(ps->dpy, ps->scr); - xcb_prefetch_extension_data(ps->c, &xcb_render_id); xcb_prefetch_extension_data(ps->c, &xcb_composite_id); xcb_prefetch_extension_data(ps->c, &xcb_damage_id); @@ -2477,6 +1712,31 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } } + if (ps->o.debug_mode && !ps->o.experimental_backends) { + log_fatal("Debug mode only works with the experimental backends."); + return NULL; + } + + ps->atoms = init_atoms(ps->c); + ps->atoms_wintypes[WINTYPE_UNKNOWN] = 0; +#define SET_WM_TYPE_ATOM(x) \ + ps->atoms_wintypes[WINTYPE_##x] = ps->atoms->a_NET_WM_WINDOW_TYPE_##x + SET_WM_TYPE_ATOM(DESKTOP); + SET_WM_TYPE_ATOM(DOCK); + SET_WM_TYPE_ATOM(TOOLBAR); + SET_WM_TYPE_ATOM(MENU); + SET_WM_TYPE_ATOM(UTILITY); + SET_WM_TYPE_ATOM(SPLASH); + SET_WM_TYPE_ATOM(DIALOG); + SET_WM_TYPE_ATOM(NORMAL); + SET_WM_TYPE_ATOM(DROPDOWN_MENU); + SET_WM_TYPE_ATOM(POPUP_MENU); + SET_WM_TYPE_ATOM(TOOLTIP); + SET_WM_TYPE_ATOM(NOTIFICATION); + SET_WM_TYPE_ATOM(COMBO); + SET_WM_TYPE_ATOM(DND); +#undef SET_WM_TYPE_ATOM + // Get needed atoms for c2 condition lists if (!(c2_list_postprocess(ps, ps->o.unredir_if_possible_blacklist) && c2_list_postprocess(ps, ps->o.paint_blacklist) && @@ -2490,7 +1750,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, "might not work"); } - ps->gaussian_map = gaussian_kernel(ps->o.shadow_radius); + ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius); sum_kernel_preprocess(ps->gaussian_map); rebuild_shadow_exclude_reg(ps); @@ -2548,7 +1808,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } if (ps->o.xrender_sync_fence) { - ps->sync_fence = xcb_generate_id(ps->c); + ps->sync_fence = x_new_id(ps->c); e = xcb_request_check( ps->c, xcb_sync_create_fence(ps->c, ps->root, ps->sync_fence, 0)); if (e) { @@ -2566,7 +1826,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, log_fatal("No XRandR extension. sw-opti, refresh-rate or " "xinerama-shadow-crop " "cannot be enabled."); - exit(1); + goto err; } } @@ -2578,9 +1838,24 @@ static session_t *session_init(int argc, char **argv, Display *dpy, rebuild_screen_reg(ps); + // Create registration window + if (!ps->o.debug_mode && !register_cm(ps)) { + exit(1); + } + // Overlay must be initialized before double buffer, and before creation // of OpenGL context. - init_overlay(ps); + if (!ps->o.debug_mode) { + if (!init_overlay(ps)) { + goto err; + } + } else { + if (!init_debug_window(ps)) { + goto err; + } + } + + ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root); // Initialize filters, must be preceded by OpenGL context creation if (!ps->o.experimental_backends && !init_render(ps)) { @@ -2596,10 +1871,10 @@ static session_t *session_init(int argc, char **argv, Display *dpy, free(config_file_to_free); if (bkend_use_glx(ps) && !ps->o.experimental_backends) { - auto glx_logger = glx_string_marker_logger_new(); - if (glx_logger) { + auto gl_logger = gl_string_marker_logger_new(); + if (gl_logger) { log_info("Enabling gl string marker"); - log_add_target_tls(glx_logger); + log_add_target_tls(gl_logger); } } @@ -2623,12 +1898,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, cxinerama_upd_scrs(ps); - // Create registration window - if (!ps->reg_win && !register_cm(ps)) - exit(1); - - init_atoms(ps); - { xcb_render_create_picture_value_list_t pa = { .subwindowmode = IncludeInferiors, @@ -2680,7 +1949,17 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ev_set_priority(&ps->event_check, EV_MINPRI); ev_prepare_start(ps->loop, &ps->event_check); - xcb_grab_server(ps->c); + e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); + if (e) { + log_fatal("Failed to grab X server"); + goto err; + } + + // We are going to pull latest information from X server now, events sent by X + // earlier is irrelavant 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); // Initialize DBus. We need to do this early, because add_win might call dbus // functions @@ -2712,25 +1991,17 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } for (int i = 0; i < nchildren; i++) { - add_win(ps, children[i], i ? children[i - 1] : XCB_NONE); - } - - for (win *i = ps->list; i; i = i->next) { - if (i->a.map_state == XCB_MAP_STATE_VIEWABLE) { - map_win(ps, i); - } + add_win_above(ps, children[i], i ? children[i - 1] : XCB_NONE); } - free(reply); + log_trace("Initial stack:"); - for (win *c = ps->list; c; c = c->next) { - log_trace("%#010x \"%s\"", c->id, c->name); + list_foreach(struct win, w, &ps->window_stack, stack_neighbour) { + log_trace("%#010x", w->id); } } - if (ps->o.track_focus) { - recheck_focus(ps); - } + ps->pending_updates = true; e = xcb_request_check(ps->c, xcb_ungrab_server(ps->c)); if (e) { @@ -2745,6 +2016,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy, log_remove_target_tls(stderr_logger); } return ps; +err: + free(ps); + return NULL; } /** @@ -2756,7 +2030,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy, * @param ps session to destroy */ static void session_destroy(session_t *ps) { - redir_stop(ps); + if (ps->redirected) { + redir_stop(ps); + } // Stop listening to events on root window xcb_change_window_attributes(ps->c, ps->root, XCB_CW_EVENT_MASK, @@ -2771,22 +2047,20 @@ static void session_destroy(session_t *ps) { #endif // Free window linked list - { - win *next = NULL; - win *list = ps->list; - ps->list = NULL; - - for (win *w = list; w; w = next) { - next = w->next; - if (w->state != WSTATE_DESTROYING) { - win_ev_stop(ps, w); - } + list_foreach_safe(struct win, w, &ps->window_stack, stack_neighbour) { + if (!w->destroyed) { + win_ev_stop(ps, w); + HASH_DEL(ps->windows, w); + } - free_win_res(ps, w); - free(w); + if (w->managed) { + auto mw = (struct managed_win *)w; + free_win_res(ps, mw); } + free(w); } + list_init_head(&ps->window_stack); // Free blacklists free_wincondlst(&ps->o.shadow_blacklist); @@ -2840,10 +2114,10 @@ static void session_destroy(session_t *ps) { free(ps->o.write_pid_path); free(ps->o.logpath); - for (int i = 0; i < MAX_BLUR_PASS; ++i) { + for (int i = 0; i < ps->o.blur_kernel_count; ++i) { free(ps->o.blur_kerns[i]); - free(ps->blur_kerns_cache[i]); } + free(ps->o.blur_kerns); free(ps->o.glx_fshader_win_str); free_xinerama_info(ps); @@ -2879,10 +2153,18 @@ static void session_destroy(session_t *ps) { deinit_render(ps); } +#if CONFIG_OPENGL + if (glx_has_context(ps)) { + // GLX context created, but not for rendering + glx_destroy(ps); + } +#endif + // Flush all events x_sync(ps->c); ev_io_stop(ps->loop, &ps->xiow); free_conv(ps->gaussian_map); + destroy_atoms(ps->atoms); #ifdef DEBUG_XRC // Report about resource leakage @@ -2926,6 +2208,7 @@ int main(int argc, char **argv) { // Set locale so window names with special characters are interpreted // correctly setlocale(LC_ALL, ""); + log_init_tls(); int exit_code; char *config_file = NULL; @@ -2968,7 +2251,7 @@ int main(int argc, char **argv) { bool quit = false; Display *dpy = XOpenDisplay(NULL); if (!dpy) { - log_fatal("Can't open display."); + fprintf(stderr, "Can't open display."); return 1; } XSetEventQueueOwner(dpy, XCBOwnsEventQueue); diff --git a/src/compton.h b/src/compton.h index 572d048a0c..1155bcbca4 100644 --- a/src/compton.h +++ b/src/compton.h @@ -25,6 +25,10 @@ #include "win.h" #include "x.h" +enum root_flags { + ROOT_FLAGS_SCREEN_CHANGE = 1 +}; + // == Functions == // TODO move static inline functions that are only used in compton.c, into // compton.c @@ -34,11 +38,28 @@ void add_damage(session_t *ps, const region_t *damage); -long determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode); +uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode); xcb_window_t find_client_win(session_t *ps, xcb_window_t w); -win *find_toplevel2(session_t *ps, xcb_window_t wid); +/// Handle configure event of a root window +void configure_root(session_t *ps, int width, int height); + +void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce); + +void update_refresh_rate(session_t *ps); + +void root_damaged(session_t *ps); + +void cxinerama_upd_scrs(session_t *ps); + +void queue_redraw(session_t *ps); + +void discard_ignore(session_t *ps, unsigned long sequence); + +void set_root_flags(session_t *ps, uint64_t flags); + +xcb_window_t session_get_target_window(session_t *); /** * Set a switch_t array of all unset wintypes to true. @@ -79,7 +100,7 @@ static inline void free_wincondlst(c2_lptr_t **pcondlst) { #ifndef CONFIG_OPENGL static inline void free_paint_glx(session_t *ps, paint_t *p) { } -static inline void free_win_res_glx(session_t *ps, win *w) { +static inline void free_win_res_glx(session_t *ps, struct managed_win *w) { } #endif @@ -90,7 +111,7 @@ static inline XTextProperty *make_text_prop(session_t *ps, char *str) { XTextProperty *pprop = ccalloc(1, XTextProperty); if (XmbTextListToTextProperty(ps->dpy, &str, 1, XStringStyle, pprop)) { - cxfree(pprop->value); + XFree(pprop->value); free(pprop); pprop = NULL; } @@ -110,8 +131,8 @@ wid_set_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop_atom, char *s } XSetTextProperty(ps->dpy, wid, pprop, prop_atom); - cxfree(pprop->value); - cxfree(pprop); + XFree(pprop->value); + XFree(pprop); return true; } @@ -129,5 +150,3 @@ static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) { drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth); free(r); } - -// vim: set et sw=2 : diff --git a/src/config.c b/src/config.c index fac6795c54..536c1b3e1c 100644 --- a/src/config.c +++ b/src/config.c @@ -3,6 +3,7 @@ // Copyright (c) 2013 Richard Grenville #include +#include #include #include #include @@ -42,6 +43,22 @@ bool parse_long(const char *s, long *dest) { return true; } +/** + * Parse an int number. + */ +bool parse_int(const char *s, int *dest) { + long val; + if (!parse_long(s, &val)) { + return false; + } + if (val > INT_MAX || val < INT_MIN) { + log_error("Number exceeded int limits: %ld", val); + return false; + } + *dest = (int)val; + return true; +} + /** * Parse a floating-point number in from a string, * also strips the trailing space and comma after the number. @@ -64,6 +81,19 @@ const char *parse_readnum(const char *src, double *dest) { return pc; } +enum blur_method parse_blur_method(const char *src) { + if (strcmp(src, "kernel") == 0) { + return BLUR_METHOD_KERNEL; + } else if (strcmp(src, "box") == 0) { + return BLUR_METHOD_BOX; + } else if (strcmp(src, "gaussian") == 0) { + return BLUR_METHOD_GAUSSIAN; + } else if (strcmp(src, "none") == 0) { + return BLUR_METHOD_NONE; + } + return BLUR_METHOD_INVALID; +} + /** * Parse a matrix. * @@ -82,11 +112,11 @@ conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) { if (src == (pc = parse_readnum(src, &val))) goto err1; src = pc; - width = val; + width = (int)val; if (src == (pc = parse_readnum(src, &val))) goto err1; src = pc; - height = val; + height = (int)val; // Validate matrix width and height if (width <= 0 || height <= 0) { @@ -102,14 +132,14 @@ conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) { "rendering, and/or consume lots of memory"); // Allocate memory - conv *matrix = cvalloc(sizeof(conv) + width * height * sizeof(double)); + conv *matrix = cvalloc(sizeof(conv) + (size_t)(width * height) * sizeof(double)); // Read elements int skip = height / 2 * width + width / 2; for (int i = 0; i < width * height; ++i) { // Ignore the center element if (i == skip) { - matrix->data[i] = 0; + matrix->data[i] = 1; continue; } if (src == (pc = parse_readnum(src, &val))) { @@ -163,13 +193,10 @@ conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) { * Parse a list of convolution kernels. * * @param[in] src string to parse - * @param[out] dest pointer to an array of kernels, must points to an array - * of `max` elements. - * @param[in] max maximum number of kernels supported * @param[out] hasneg whether any of the kernels have negative values - * @return if the `src` string is a valid kernel list string + * @return the kernels */ -bool parse_blur_kern_lst(const char *src, conv **dest, int max, bool *hasneg) { +struct conv **parse_blur_kern_lst(const char *src, bool *hasneg, int *count) { // TODO just return a predefined kernels, not parse predefined strings... static const struct { const char *name; @@ -224,49 +251,53 @@ bool parse_blur_kern_lst(const char *src, conv **dest, int max, bool *hasneg) { "000000,"}, }; + *count = 0; *hasneg = false; for (unsigned int i = 0; i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i) { if (!strcmp(CONV_KERN_PREDEF[i].name, src)) - return parse_blur_kern_lst(CONV_KERN_PREDEF[i].kern_str, dest, - max, hasneg); + return parse_blur_kern_lst(CONV_KERN_PREDEF[i].kern_str, hasneg, count); + } + + int nkernels = 1; + for (int i = 0; src[i]; i++) { + if (src[i] == ';') { + nkernels++; + } } + struct conv **ret = ccalloc(nkernels, struct conv *); + int i = 0; const char *pc = src; - // Free old kernels - for (i = 0; i < max; ++i) { - free(dest[i]); - dest[i] = NULL; - } - // Continue parsing until the end of source string i = 0; - while (pc && *pc && i < max - 1) { + while (pc && *pc) { bool tmp_hasneg; - dest[i] = parse_blur_kern(pc, &pc, &tmp_hasneg); - if (!dest[i]) { - return false; + assert(i < nkernels); + ret[i] = parse_blur_kern(pc, &pc, &tmp_hasneg); + if (!ret[i]) { + for (int j = 0; j < i; j++) { + free(ret[j]); + } + free(ret); + return NULL; } i++; *hasneg |= tmp_hasneg; } if (i > 1) { - log_warn("You are seeing this message because your are using " - "multipassblur. Please " - "report an issue to us so we know multipass blur is actually " - "been used. " - "Otherwise it might be removed in future releases"); + log_warn("You are seeing this message because you are using " + "multipass blur. Please report an issue to us so we know " + "multipass blur is actually been used. Otherwise it might be " + "removed in future releases"); } - if (*pc) { - log_error("Too many blur kernels!"); - return false; - } + *count = i; - return true; + return ret; } /** @@ -276,70 +307,76 @@ bool parse_blur_kern_lst(const char *src, conv **dest, int max, bool *hasneg) { */ bool parse_geometry(session_t *ps, const char *src, region_t *dest) { pixman_region32_clear(dest); - if (!src) + if (!src) { return true; - if (!ps->root_width || !ps->root_height) + } + if (!ps->root_width || !ps->root_height) { return true; + } - geometry_t geom = {.wid = ps->root_width, .hei = ps->root_height, .x = 0, .y = 0}; + long x = 0, y = 0; + long width = ps->root_width, height = ps->root_height; long val = 0L; char *endptr = NULL; src = skip_space(src); - if (!*src) + if (!*src) { goto parse_geometry_end; + } // Parse width // Must be base 10, because "0x0..." may appear - if (!('+' == *src || '-' == *src)) { + if (*src != '+' && *src != '-') { val = strtol(src, &endptr, 10); assert(endptr); if (src != endptr) { - geom.wid = val; - if (geom.wid < 0) { + if (val < 0) { log_error("Invalid width: %s", src); return false; } + width = val; src = endptr; } src = skip_space(src); } // Parse height - if ('x' == *src) { + if (*src == 'x') { ++src; val = strtol(src, &endptr, 10); assert(endptr); if (src != endptr) { - geom.hei = val; - if (geom.hei < 0) { + if (val < 0) { log_error("Invalid height: %s", src); return false; } + height = val; src = endptr; } src = skip_space(src); } // Parse x - if ('+' == *src || '-' == *src) { + if (*src == '+' || *src == '-') { val = strtol(src, &endptr, 10); if (endptr && src != endptr) { - geom.x = val; - if (*src == '-') - geom.x += ps->root_width - geom.wid; + x = val; + if (*src == '-') { + x += ps->root_width - width; + } src = endptr; } src = skip_space(src); } // Parse y - if ('+' == *src || '-' == *src) { + if (*src == '+' || *src == '-') { val = strtol(src, &endptr, 10); if (endptr && src != endptr) { - geom.y = val; - if (*src == '-') - geom.y += ps->root_height - geom.hei; + y = val; + if (*src == '-') { + y += ps->root_height - height; + } src = endptr; } src = skip_space(src); @@ -351,7 +388,16 @@ bool parse_geometry(session_t *ps, const char *src, region_t *dest) { } parse_geometry_end: - pixman_region32_union_rect(dest, dest, geom.x, geom.y, geom.wid, geom.hei); + if (x < INT_MIN || x > INT_MAX || y < INT_MIN || y > INT_MAX) { + log_error("Geometry coordinates exceeded limits: %s", src); + return false; + } + if (width > UINT_MAX || height > UINT_MAX) { + // less than 0 is checked for earlier + log_error("Geometry size exceeded limits: %s", src); + return false; + } + pixman_region32_union_rect(dest, dest, (int)x, (int)y, (uint)width, (uint)height); return true; } @@ -448,6 +494,71 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) { + *opt = (struct options){ + .backend = BKEND_XRENDER, + .glx_no_stencil = false, + .mark_wmwin_focused = false, + .mark_ovredir_focused = false, + .detect_rounded_corners = false, + .resize_damage = 0, + .unredir_if_possible = false, + .unredir_if_possible_blacklist = NULL, + .unredir_if_possible_delay = 0, + .redirected_force = UNSET, + .stoppaint_force = UNSET, + .dbus = false, + .benchmark = 0, + .benchmark_wid = XCB_NONE, + .logpath = NULL, + + .refresh_rate = 0, + .sw_opti = false, + + .shadow_red = 0.0, + .shadow_green = 0.0, + .shadow_blue = 0.0, + .shadow_radius = 18, + .shadow_offset_x = -15, + .shadow_offset_y = -15, + .shadow_opacity = .75, + .shadow_blacklist = NULL, + .shadow_ignore_shaped = false, + .respect_prop_shadow = false, + .xinerama_shadow_crop = false, + + .fade_in_step = 0.028, + .fade_out_step = 0.03, + .fade_delta = 10, + .no_fading_openclose = false, + .no_fading_destroyed_argb = false, + .fade_blacklist = NULL, + + .inactive_opacity = 1.0, + .inactive_opacity_override = false, + .active_opacity = 1.0, + .frame_opacity = 1.0, + .detect_client_opacity = false, + + .blur_method = BLUR_METHOD_NONE, + .blur_background_frame = false, + .blur_background_fixed = false, + .blur_background_blacklist = NULL, + .blur_kerns = NULL, + .blur_kernel_count = 0, + .inactive_dim = 0.0, + .inactive_dim_fixed = false, + .invert_color_list = NULL, + .opacity_rules = NULL, + + .use_ewmh_active_win = false, + .focus_blacklist = NULL, + .detect_transient = false, + .detect_client_leader = false, + + .track_wdata = false, + .track_leader = false, + }; + char *ret = NULL; #ifdef CONFIG_LIBCONFIG ret = parse_config_libconfig(opt, config_file, shadow_enable, fading_enable, diff --git a/src/config.h b/src/config.h index 4c88ad0b65..ceb87854a4 100644 --- a/src/config.h +++ b/src/config.h @@ -19,6 +19,7 @@ #include #endif +#include "backend/backend.h" #include "compiler.h" #include "kernel.h" #include "log.h" @@ -56,17 +57,13 @@ typedef struct win_option { typedef struct _c2_lptr c2_lptr_t; -// This macro is here because this is the maximum number -// of blur passes options_t can hold, not a limitation of -// rendering. -/// @brief Maximum passes for blur. -#define MAX_BLUR_PASS 5 - /// Structure representing all options. -typedef struct options_t { +typedef struct options { // === Debugging === bool monitor_repaint; bool print_diagnostics; + /// Render to a separate window instead of taking over the screen + bool debug_mode; // === General === /// Use the experimental new backends? bool experimental_backends; @@ -97,7 +94,7 @@ typedef struct options_t { /// when determining if a window could be unredirected. c2_lptr_t *unredir_if_possible_blacklist; /// Delay before unredirecting screen, in milliseconds. - unsigned long unredir_if_possible_delay; + long unredir_if_possible_delay; /// Forced redirection setting through D-Bus. switch_t redirected_force; /// Whether to stop painting. Controlled through D-Bus. @@ -155,7 +152,7 @@ typedef struct options_t { /// How much to fade out in a single fading step. double fade_out_step; /// Fading time delta. In milliseconds. - unsigned long fade_delta; + int fade_delta; /// Whether to disable fading on window open/close. bool no_fading_openclose; /// Whether to disable fading on ARGB managed destroyed windows. @@ -180,8 +177,12 @@ typedef struct options_t { bool detect_client_opacity; // === Other window processing === - /// Whether to blur background of semi-transparent / ARGB windows. - bool blur_background; + /// Blur method for background of semi-transparent windows + enum blur_method blur_method; + // Size of the blur kernel + int blur_radius; + // Standard deviation for the gaussian blur + double blur_deviation; /// Whether to blur background when the window frame is not opaque. /// Implies blur_background. bool blur_background_frame; @@ -191,7 +192,9 @@ typedef struct options_t { /// Background blur blacklist. A linked list of conditions. c2_lptr_t *blur_background_blacklist; /// Blur convolution kernel. - conv *blur_kerns[MAX_BLUR_PASS]; + struct conv **blur_kerns; + /// Number of convolution kernels + int blur_kernel_count; /// How much to dim an inactive window. 0.0 - 1.0, 0 to disable. double inactive_dim; /// Whether to use fixed inactive dim opacity, instead of deciding @@ -217,8 +220,6 @@ typedef struct options_t { bool detect_client_leader; // === Calculated === - /// Whether compton needs to track focus changes. - bool track_focus; /// Whether compton needs to track window name and class. bool track_wdata; /// Whether compton needs to track window leaders. @@ -227,10 +228,12 @@ typedef struct options_t { extern const char *const BACKEND_STRS[NUM_BKEND + 1]; -attr_warn_unused_result bool parse_long(const char *, long *); -attr_warn_unused_result bool parse_blur_kern_lst(const char *, conv **, int, bool *hasneg); -attr_warn_unused_result bool parse_geometry(session_t *, const char *, region_t *); -attr_warn_unused_result bool parse_rule_opacity(c2_lptr_t **, const char *); +bool must_use parse_long(const char *, long *); +bool must_use parse_int(const char *, int *); +struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count); +bool must_use parse_geometry(session_t *, const char *, region_t *); +bool must_use parse_rule_opacity(c2_lptr_t **, const char *); +enum blur_method must_use parse_blur_method(const char *src); /** * Add a pattern to a condition linked list. diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 5cbcba7d08..4a6bbb5fcd 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -295,7 +295,12 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // --detect-client-opacity lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity); // --refresh-rate - config_lookup_int(&cfg, "refresh-rate", &opt->refresh_rate); + if (config_lookup_int(&cfg, "refresh-rate", &opt->refresh_rate)) { + if (opt->refresh_rate < 0) { + log_warn("Invalid refresh rate %d, fallback to 0", opt->refresh_rate); + opt->refresh_rate = 0; + } + } // --vsync if (config_lookup_string(&cfg, "vsync", &sval)) { opt->vsync = parse_vsync(sval); @@ -337,8 +342,13 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // --unredir-if-possible lcfg_lookup_bool(&cfg, "unredir-if-possible", &opt->unredir_if_possible); // --unredir-if-possible-delay - if (config_lookup_int(&cfg, "unredir-if-possible-delay", &ival)) - opt->unredir_if_possible_delay = ival; + if (config_lookup_int(&cfg, "unredir-if-possible-delay", &ival)) { + if (ival < 0) { + log_warn("Invalid unredir-if-possible-delay %d", ival); + } else { + opt->unredir_if_possible_delay = ival; + } + } // --inactive-dim-fixed lcfg_lookup_bool(&cfg, "inactive-dim-fixed", &opt->inactive_dim_fixed); // --detect-transient @@ -361,16 +371,21 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad parse_cfg_condlst(&cfg, &opt->unredir_if_possible_blacklist, "unredir-if-possible-exclude"); // --blur-background - lcfg_lookup_bool(&cfg, "blur-background", &opt->blur_background); + if (config_lookup_bool(&cfg, "blur-background", &ival) && ival) { + opt->blur_method = BLUR_METHOD_KERNEL; + } // --blur-background-frame lcfg_lookup_bool(&cfg, "blur-background-frame", &opt->blur_background_frame); // --blur-background-fixed lcfg_lookup_bool(&cfg, "blur-background-fixed", &opt->blur_background_fixed); // --blur-kern - if (config_lookup_string(&cfg, "blur-kern", &sval) && - !parse_blur_kern_lst(sval, opt->blur_kerns, MAX_BLUR_PASS, conv_kern_hasneg)) { - log_fatal("Cannot parse \"blur-kern\""); - goto err; + if (config_lookup_string(&cfg, "blur-kern", &sval)) { + opt->blur_kerns = + parse_blur_kern_lst(sval, conv_kern_hasneg, &opt->blur_kernel_count); + if (!opt->blur_kerns) { + log_fatal("Cannot parse \"blur-kern\""); + goto err; + } } // --resize-damage config_lookup_int(&cfg, "resize-damage", &opt->resize_damage); @@ -435,6 +450,32 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad return ERR_PTR(-1); } + config_setting_t *blur_cfg = config_lookup(&cfg, "blur"); + if (blur_cfg) { + if (config_setting_lookup_string(blur_cfg, "method", &sval)) { + enum blur_method method = parse_blur_method(sval); + if (method >= BLUR_METHOD_INVALID) { + log_warn("Invalid blur method %s, ignoring.", sval); + } else { + opt->blur_method = method; + } + } + + opt->blur_radius = -1; + config_setting_lookup_int(blur_cfg, "size", &opt->blur_radius); + + if (config_setting_lookup_string(blur_cfg, "kernel", &sval)) { + opt->blur_kerns = parse_blur_kern_lst(sval, conv_kern_hasneg, + &opt->blur_kernel_count); + if (!opt->blur_kerns) { + log_warn("Failed to parse blur kernel: %s", sval); + } + } + + opt->blur_deviation = 0.84089642; + config_setting_lookup_float(blur_cfg, "deviation", &opt->blur_deviation); + } + // Wintype settings // XXX ! Refactor all the wintype_* arrays into a struct diff --git a/src/dbus.c b/src/dbus.c index 2cdf46594b..05c84c1145 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -21,9 +21,11 @@ #include "common.h" #include "compiler.h" #include "config.h" +#include "list.h" #include "log.h" #include "string_utils.h" #include "types.h" +#include "uthash_extra.h" #include "utils.h" #include "win.h" @@ -41,9 +43,9 @@ typedef uint32_t cdbus_window_t; #define CDBUS_TYPE_WINDOW DBUS_TYPE_UINT32 #define CDBUS_TYPE_WINDOW_STR DBUS_TYPE_UINT32_AS_STRING -typedef uint16_t cdbus_enum_t; -#define CDBUS_TYPE_ENUM DBUS_TYPE_UINT16 -#define CDBUS_TYPE_ENUM_STR DBUS_TYPE_UINT16_AS_STRING +typedef uint32_t cdbus_enum_t; +#define CDBUS_TYPE_ENUM DBUS_TYPE_UINT32 +#define CDBUS_TYPE_ENUM_STR DBUS_TYPE_UINT32_AS_STRING #define CDBUS_SERVICE_NAME "com.github.chjj.compton" #define CDBUS_INTERFACE_NAME CDBUS_SERVICE_NAME @@ -465,10 +467,9 @@ static bool cdbus_apdarg_string(session_t *ps, DBusMessage *msg, const void *dat static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data) { // Get the number of wids we are to include unsigned count = 0; - for (win *w = ps->list; w; w = w->next) { - if (w->state != WSTATE_DESTROYING) { - ++count; - } + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + ++count; } if (!count) { @@ -480,17 +481,13 @@ static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data) auto arr = ccalloc(count, cdbus_window_t); // Build the array - { - cdbus_window_t *pcur = arr; - for (win *w = ps->list; w; w = w->next) { - if (w->state != WSTATE_DESTROYING) { - *pcur = w->id; - ++pcur; - assert(pcur <= arr + count); - } - } - assert(pcur == arr + count); + cdbus_window_t *pcur = arr; + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + *pcur = w->id; + ++pcur; } + assert(pcur == arr + count); // Append arguments if (!dbus_message_append_args(msg, DBUS_TYPE_ARRAY, CDBUS_TYPE_WINDOW, &arr, @@ -610,6 +607,14 @@ static inline bool cdbus_reply_int32(session_t *ps, DBusMessage *srcmsg, int32_t return cdbus_reply(ps, srcmsg, cdbus_apdarg_int32, &val); } +/** + * Send a reply with an int32 argument, cast from a long. + */ +static inline bool cdbus_reply_int32l(session_t *ps, DBusMessage *srcmsg, long val) { + int32_t tmp = (int32_t)val; + return cdbus_reply(ps, srcmsg, cdbus_apdarg_int32, &tmp); +} + /** * Send a reply with an uint32 argument. */ @@ -738,7 +743,7 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) { return false; } - win *w = find_win(ps, wid); + auto w = find_managed_win(ps, wid); if (!w) { log_error("Window %#010x not found.", wid); @@ -747,16 +752,21 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) { } #define cdbus_m_win_get_do(tgt, apdarg_func) \ - if (!strcmp(MSTR(tgt), target)) { \ + if (!strcmp(#tgt, target)) { \ apdarg_func(ps, msg, w->tgt); \ return true; \ } - cdbus_m_win_get_do(id, cdbus_reply_wid); + cdbus_m_win_get_do(base.id, cdbus_reply_wid); // next if (!strcmp("next", target)) { - cdbus_reply_wid(ps, msg, (w->next ? w->next->id : 0)); + cdbus_reply_wid( + ps, msg, + (list_node_is_last(&ps->window_stack, &w->base.stack_neighbour) + ? 0 + : list_entry(w->base.stack_neighbour.next, struct win, stack_neighbour) + ->id)); return true; } @@ -786,28 +796,28 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) { cdbus_m_win_get_do(class_general, cdbus_reply_string); cdbus_m_win_get_do(role, cdbus_reply_string); - cdbus_m_win_get_do(opacity, cdbus_reply_uint32); - cdbus_m_win_get_do(opacity_tgt, cdbus_reply_uint32); + cdbus_m_win_get_do(opacity, cdbus_reply_double); + cdbus_m_win_get_do(opacity_tgt, cdbus_reply_double); cdbus_m_win_get_do(has_opacity_prop, cdbus_reply_bool); cdbus_m_win_get_do(opacity_prop, cdbus_reply_uint32); cdbus_m_win_get_do(opacity_is_set, cdbus_reply_bool); - cdbus_m_win_get_do(opacity_set, cdbus_reply_uint32); + cdbus_m_win_get_do(opacity_set, cdbus_reply_double); cdbus_m_win_get_do(frame_opacity, cdbus_reply_double); if (!strcmp("left_width", target)) { - cdbus_reply_uint32(ps, msg, w->frame_extents.left); + cdbus_reply_int32(ps, msg, w->frame_extents.left); return true; } if (!strcmp("right_width", target)) { - cdbus_reply_uint32(ps, msg, w->frame_extents.right); + cdbus_reply_int32(ps, msg, w->frame_extents.right); return true; } if (!strcmp("top_width", target)) { - cdbus_reply_uint32(ps, msg, w->frame_extents.top); + cdbus_reply_int32(ps, msg, w->frame_extents.top); return true; } if (!strcmp("bottom_width", target)) { - cdbus_reply_uint32(ps, msg, w->frame_extents.bottom); + cdbus_reply_int32(ps, msg, w->frame_extents.bottom); return true; } @@ -837,7 +847,7 @@ static bool cdbus_process_win_set(session_t *ps, DBusMessage *msg) { return false; } - win *w = find_win(ps, wid); + auto w = find_managed_win(ps, wid); if (!w) { log_error("Window %#010x not found.", wid); @@ -914,15 +924,16 @@ static bool cdbus_process_find_win(session_t *ps, DBusMessage *msg) { cdbus_window_t client = XCB_NONE; if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_WINDOW, &client)) return false; - win *w = find_toplevel(ps, client); - if (w) - wid = w->id; + auto w = find_toplevel(ps, client); + if (w) { + wid = w->base.id; + } } // Find focused window else if (!strcmp("focused", target)) { - win *w = find_focused(ps); - if (w) - wid = w->id; + if (ps->active_win && ps->active_win->state != WSTATE_UNMAPPED) { + wid = ps->active_win->base.id; + } } else { log_error(CDBUS_ERROR_BADTGT_S, target); cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target); @@ -945,13 +956,13 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { return false; #define cdbus_m_opts_get_do(tgt, apdarg_func) \ - if (!strcmp(MSTR(tgt), target)) { \ + if (!strcmp(#tgt, target)) { \ apdarg_func(ps, msg, ps->o.tgt); \ return true; \ } #define cdbus_m_opts_get_stub(tgt, apdarg_func, ret) \ - if (!strcmp(MSTR(tgt), target)) { \ + if (!strcmp(#tgt, target)) { \ apdarg_func(ps, msg, ret); \ return true; \ } @@ -986,7 +997,7 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { return true; } cdbus_m_opts_get_do(unredir_if_possible, cdbus_reply_bool); - cdbus_m_opts_get_do(unredir_if_possible_delay, cdbus_reply_int32); + cdbus_m_opts_get_do(unredir_if_possible_delay, cdbus_reply_int32l); cdbus_m_opts_get_do(redirected_force, cdbus_reply_enum); cdbus_m_opts_get_do(stoppaint_force, cdbus_reply_enum); cdbus_m_opts_get_do(logpath, cdbus_reply_string); @@ -1010,11 +1021,11 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { cdbus_m_opts_get_do(xinerama_shadow_crop, cdbus_reply_bool); cdbus_m_opts_get_do(fade_delta, cdbus_reply_int32); - cdbus_m_opts_get_do(fade_in_step, cdbus_reply_int32); - cdbus_m_opts_get_do(fade_out_step, cdbus_reply_int32); + cdbus_m_opts_get_do(fade_in_step, cdbus_reply_double); + cdbus_m_opts_get_do(fade_out_step, cdbus_reply_double); cdbus_m_opts_get_do(no_fading_openclose, cdbus_reply_bool); - cdbus_m_opts_get_do(blur_background, cdbus_reply_bool); + cdbus_m_opts_get_do(blur_method, cdbus_reply_bool); cdbus_m_opts_get_do(blur_background_frame, cdbus_reply_bool); cdbus_m_opts_get_do(blur_background_fixed, cdbus_reply_bool); @@ -1031,7 +1042,7 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { cdbus_m_opts_get_do(use_damage, cdbus_reply_bool); #endif - cdbus_m_opts_get_do(track_focus, cdbus_reply_bool); + cdbus_m_opts_get_stub(track_focus, cdbus_reply_bool, true); cdbus_m_opts_get_do(track_wdata, cdbus_reply_bool); cdbus_m_opts_get_do(track_leader, cdbus_reply_bool); #undef cdbus_m_opts_get_do @@ -1056,7 +1067,7 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { return false; #define cdbus_m_opts_set_do(tgt, type, real_type) \ - if (!strcmp(MSTR(tgt), target)) { \ + if (!strcmp(#tgt, target)) { \ real_type val; \ if (!cdbus_msg_get_arg(msg, 1, type, &val)) \ return false; \ @@ -1066,10 +1077,14 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { // fade_delta if (!strcmp("fade_delta", target)) { - int32_t val = 0.0; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_INT32, &val)) + int32_t val = 0; + if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_INT32, &val)) { return false; - ps->o.fade_delta = max_i(val, 1); + } + if (val <= 0) { + return false; + } + ps->o.fade_delta = max2(val, 1); goto cdbus_process_opts_set_success; } @@ -1113,18 +1128,12 @@ static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) { } // clear_shadow - if (!strcmp("clear_shadow", target)) + if (!strcmp("clear_shadow", target)) { goto cdbus_process_opts_set_success; + } // track_focus if (!strcmp("track_focus", target)) { - dbus_bool_t val = FALSE; - if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val)) - return false; - // You could enable this option, but never turn if off - if (val) { - opts_init_track_focus(ps); - } goto cdbus_process_opts_set_success; } @@ -1289,37 +1298,37 @@ static DBusHandlerResult cdbus_process(DBusConnection *c, DBusMessage *msg, void /** @name Core callbacks */ ///@{ -void cdbus_ev_win_added(session_t *ps, win *w) { +void cdbus_ev_win_added(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; if (cd->dbus_conn) cdbus_signal_wid(ps, "win_added", w->id); } -void cdbus_ev_win_destroyed(session_t *ps, win *w) { +void cdbus_ev_win_destroyed(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; if (cd->dbus_conn) cdbus_signal_wid(ps, "win_destroyed", w->id); } -void cdbus_ev_win_mapped(session_t *ps, win *w) { +void cdbus_ev_win_mapped(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; if (cd->dbus_conn) cdbus_signal_wid(ps, "win_mapped", w->id); } -void cdbus_ev_win_unmapped(session_t *ps, win *w) { +void cdbus_ev_win_unmapped(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; if (cd->dbus_conn) cdbus_signal_wid(ps, "win_unmapped", w->id); } -void cdbus_ev_win_focusout(session_t *ps, win *w) { +void cdbus_ev_win_focusout(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; if (cd->dbus_conn) cdbus_signal_wid(ps, "win_focusout", w->id); } -void cdbus_ev_win_focusin(session_t *ps, win *w) { +void cdbus_ev_win_focusin(session_t *ps, struct win *w) { struct cdbus_data *cd = ps->dbus_data; if (cd->dbus_conn) cdbus_signal_wid(ps, "win_focusin", w->id); diff --git a/src/dbus.h b/src/dbus.h index d60b47e085..54a58af52c 100644 --- a/src/dbus.h +++ b/src/dbus.h @@ -14,7 +14,7 @@ #include typedef struct session session_t; -typedef struct win win; +struct win; /** * Return a string representation of a D-Bus message type. @@ -34,21 +34,21 @@ bool cdbus_init(session_t *ps, const char *uniq_name); void cdbus_destroy(session_t *ps); /// Generate dbus win_added signal -void cdbus_ev_win_added(session_t *ps, win *w); +void cdbus_ev_win_added(session_t *ps, struct win *w); /// Generate dbus win_destroyed signal -void cdbus_ev_win_destroyed(session_t *ps, win *w); +void cdbus_ev_win_destroyed(session_t *ps, struct win *w); /// Generate dbus win_mapped signal -void cdbus_ev_win_mapped(session_t *ps, win *w); +void cdbus_ev_win_mapped(session_t *ps, struct win *w); /// Generate dbus win_unmapped signal -void cdbus_ev_win_unmapped(session_t *ps, win *w); +void cdbus_ev_win_unmapped(session_t *ps, struct win *w); /// Generate dbus win_focusout signal -void cdbus_ev_win_focusout(session_t *ps, win *w); +void cdbus_ev_win_focusout(session_t *ps, struct win *w); /// Generate dbus win_focusin signal -void cdbus_ev_win_focusin(session_t *ps, win *w); +void cdbus_ev_win_focusin(session_t *ps, struct win *w); // vim: set noet sw=8 ts=8 : diff --git a/src/diagnostic.c b/src/diagnostic.c index 7e26d9f5ba..ae31005b0a 100644 --- a/src/diagnostic.c +++ b/src/diagnostic.c @@ -4,6 +4,7 @@ #include #include +#include "backend/driver.h" #include "diagnostic.h" #include "config.h" #include "common.h" @@ -21,6 +22,8 @@ void print_diagnostics(session_t *ps, const char *config_file) { printf("* Fast Math: Yes\n"); #endif printf("* Config file used: %s\n", config_file ?: "None"); + printf("\n### Drivers (inaccurate):\n\n"); + print_drivers(ps->drivers); } // vim: set noet sw=8 ts=8 : diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000000..a948a89c4f --- /dev/null +++ b/src/event.c @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2019, Yuxuan Shui + +#include +#include +#include + +#include "atom.h" +#include "common.h" +#include "compiler.h" +#include "compton.h" +#include "event.h" +#include "utils.h" + +/// Event handling with X is complicated. Handling events with other events possibly +/// in-flight is no good. Because your internal state won't be up to date. Also, querying +/// the server while events are in-flight is not good. Because events later in the queue +/// might container information you are querying. Thus those events will cause you to do +/// unnecessary updates even when you already have the latest information (remember, you +/// 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. +/// +/// All of above mandates we do these things: +/// 1. Grab server when handling events +/// 2. Make sure the event queue is empty before we make any query to the server +/// +/// Notice (2) has a dependency circle. To handle events, you sometimes need to make +/// queries. But to make queries you have to first handle events. +/// +/// To break that circle, we split all event handling into top and bottom halves. The +/// bottom half will just look at the event itself, update as much state as they can +/// without making queries, then queue up necessary works need to be done by the top half. +/// The top half will do all the other necessary updates. Before entering the top half, we +/// grab the server and make sure the event queue is empty. +/// +/// When top half finished, we enter the render stage, where no server state should be +/// queried. All rendering should be done with our internal knowledge of the server state. +/// +/// TODO the things described above + +/** + * Get a window's name from window ID. + */ +static inline const char *ev_window_name(session_t *ps, xcb_window_t wid) { + char *name = ""; + if (wid) { + name = "(Failed to get title)"; + if (ps->root == wid) { + name = "(Root window)"; + } else if (ps->overlay == wid) { + name = "(Overlay)"; + } else { + auto w = find_managed_win(ps, wid); + if (!w) { + w = find_toplevel(ps, wid); + } + + if (w) { + win_get_name(ps, w); + if (w->name) { + name = w->name; + } + } + } + } + return name; +} + +static inline xcb_window_t attr_pure ev_window(session_t *ps, xcb_generic_event_t *ev) { + switch (ev->response_type) { + case FocusIn: + case FocusOut: return ((xcb_focus_in_event_t *)ev)->event; + case CreateNotify: return ((xcb_create_notify_event_t *)ev)->window; + case ConfigureNotify: return ((xcb_configure_notify_event_t *)ev)->window; + case DestroyNotify: return ((xcb_destroy_notify_event_t *)ev)->window; + case MapNotify: return ((xcb_map_notify_event_t *)ev)->window; + case UnmapNotify: return ((xcb_unmap_notify_event_t *)ev)->window; + case ReparentNotify: return ((xcb_reparent_notify_event_t *)ev)->window; + case CirculateNotify: return ((xcb_circulate_notify_event_t *)ev)->window; + case Expose: return ((xcb_expose_event_t *)ev)->window; + case PropertyNotify: return ((xcb_property_notify_event_t *)ev)->window; + case ClientMessage: return ((xcb_client_message_event_t *)ev)->window; + default: + if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) { + return ((xcb_damage_notify_event_t *)ev)->drawable; + } + + if (ps->shape_exists && ev->response_type == ps->shape_event) { + return ((xcb_shape_notify_event_t *)ev)->affected_window; + } + + return 0; + } +} + +static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) { + static char buf[128]; + switch (ev->response_type & 0x7f) { + CASESTRRET(FocusIn); + CASESTRRET(FocusOut); + CASESTRRET(CreateNotify); + CASESTRRET(ConfigureNotify); + CASESTRRET(DestroyNotify); + CASESTRRET(MapNotify); + CASESTRRET(UnmapNotify); + CASESTRRET(ReparentNotify); + CASESTRRET(CirculateNotify); + CASESTRRET(Expose); + CASESTRRET(PropertyNotify); + CASESTRRET(ClientMessage); + } + + if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) + return "Damage"; + + if (ps->shape_exists && ev->response_type == ps->shape_event) + return "ShapeNotify"; + + if (ps->xsync_exists) { + int o = ev->response_type - ps->xsync_event; + switch (o) { + CASESTRRET(XSyncCounterNotify); + CASESTRRET(XSyncAlarmNotify); + } + } + + sprintf(buf, "Event %d", ev->response_type); + + return buf; +} + +static inline const char *attr_pure ev_focus_mode_name(xcb_focus_in_event_t *ev) { + switch (ev->mode) { + CASESTRRET(NotifyNormal); + CASESTRRET(NotifyWhileGrabbed); + CASESTRRET(NotifyGrab); + CASESTRRET(NotifyUngrab); + } + + return "Unknown"; +} + +static inline const char *attr_pure ev_focus_detail_name(xcb_focus_in_event_t *ev) { + switch (ev->detail) { + CASESTRRET(NotifyAncestor); + CASESTRRET(NotifyVirtual); + CASESTRRET(NotifyInferior); + CASESTRRET(NotifyNonlinear); + CASESTRRET(NotifyNonlinearVirtual); + CASESTRRET(NotifyPointer); + CASESTRRET(NotifyPointerRoot); + CASESTRRET(NotifyDetailNone); + } + + return "Unknown"; +} + +static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) { + log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev), + ev_focus_detail_name(ev)); + ps->pending_updates = true; +} + +static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { + log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev), + ev_focus_detail_name(ev)); + ps->pending_updates = true; +} + +static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { + assert(ev->parent == ps->root); + add_win_top(ps, ev->window); +} + +/// Handle configure event of a regular window +static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { + auto w = find_win(ps, ce->window); + region_t damage; + pixman_region32_init(&damage); + + if (!w) { + return; + } + + if (!w->managed) { + restack_above(ps, w, ce->above_sibling); + return; + } + + auto mw = (struct managed_win *)w; + + if (mw->state == WSTATE_UNMAPPED || mw->state == WSTATE_UNMAPPING || + mw->state == WSTATE_DESTROYING) { + // Only restack the window to make sure we can handle future restack + // notification correctly + restack_above(ps, w, ce->above_sibling); + } else { + restack_above(ps, w, ce->above_sibling); + bool factor_change = false; + win_extents(mw, &damage); + + // If window geometry change, free old extents + if (mw->g.x != ce->x || mw->g.y != ce->y || mw->g.width != ce->width || + mw->g.height != ce->height || mw->g.border_width != ce->border_width) { + factor_change = true; + } + + mw->g.x = ce->x; + mw->g.y = ce->y; + + if (mw->g.width != ce->width || mw->g.height != ce->height || + mw->g.border_width != ce->border_width) { + log_trace("Window size changed, %dx%d -> %dx%d", mw->g.width, + mw->g.height, ce->width, ce->height); + mw->g.width = ce->width; + mw->g.height = ce->height; + mw->g.border_width = ce->border_width; + win_on_win_size_change(ps, mw); + win_update_bounding_shape(ps, mw); + } + + region_t new_extents; + pixman_region32_init(&new_extents); + win_extents(mw, &new_extents); + pixman_region32_union(&damage, &damage, &new_extents); + pixman_region32_fini(&new_extents); + + if (factor_change) { + win_on_factor_change(ps, mw); + add_damage(ps, &damage); + win_update_screen(ps, mw); + } + } + + pixman_region32_fini(&damage); + + // override_redirect flag cannot be changed after window creation, as far + // as I know, so there's no point to re-match windows here. + mw->a.override_redirect = ce->override_redirect; +} + +static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) { + log_debug("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }", + ev->event, ev->window, ev->above_sibling, ev->override_redirect); + if (ev->window == ps->root) { + configure_root(ps, ev->width, ev->height); + } else { + configure_win(ps, ev); + } +} + +static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) { + auto w = find_win(ps, ev->window); + if (w) { + if (w->managed) { + unmap_win(ps, (struct managed_win **)&w, true); + } else { + destroy_unmanaged_win(ps, &w); + } + } +} + +static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { + map_win_by_id(ps, ev->window); + // FocusIn/Out may be ignored when the window is unmapped, so we must + // recheck focus here + ps->pending_updates = true; // to update focus +} + +static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) { + auto w = find_managed_win(ps, ev->window); + if (w) { + unmap_win(ps, &w, false); + } +} + +static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) { + log_debug("{ new_parent: %#010x, override_redirect: %d }", ev->parent, + ev->override_redirect); + + if (ev->parent == ps->root) { + // X will generate reparent notifiy 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); + if (w) { + // This window has already been reparented to root before, + // so we don't need to create a new window for it, we just need to + // move it to the top + restack_top(ps, w); + } else { + add_win_top(ps, ev->window); + } + } else { + // otherwise, find and destroy the window first + auto w = find_win(ps, ev->window); + if (w) { + if (w->managed) { + unmap_win(ps, (struct managed_win **)&w, true); + } else { + destroy_unmanaged_win(ps, &w); + } + } + + // Reset event mask in case something wrong happens + xcb_change_window_attributes( + ps->c, ev->window, XCB_CW_EVENT_MASK, + (const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)}); + + // Check if the window is an undetected client window + // Firstly, check if it's a known client window + if (!find_toplevel(ps, ev->window)) { + // If not, look for its frame window + auto w_top = find_toplevel2(ps, ev->parent); + // If found, and the client window has not been determined, or its + // frame may not have a correct client, continue + if (w_top && + (!w_top->client_win || w_top->client_win == w_top->base.id)) { + // If it has WM_STATE, mark it the client window + if (wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) { + w_top->wmwin = false; + win_unmark_client(ps, w_top); + win_mark_client(ps, w_top, ev->window); + } + // Otherwise, watch for WM_STATE on it + else { + xcb_change_window_attributes( + ps->c, ev->window, XCB_CW_EVENT_MASK, + (const uint32_t[]){ + determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) | + XCB_EVENT_MASK_PROPERTY_CHANGE}); + } + } + } + } +} + +static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) { + auto w = find_win(ps, ev->window); + + if (!w) + return; + + if (ev->place == PlaceOnTop) { + restack_top(ps, w); + } else { + restack_bottom(ps, w); + } +} + +static inline void expose_root(session_t *ps, const rect_t *rects, int nrects) { + region_t region; + pixman_region32_init_rects(®ion, rects, nrects); + add_damage(ps, ®ion); +} + +static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) { + if (ev->window == ps->root || (ps->overlay && ev->window == ps->overlay)) { + int more = ev->count + 1; + if (ps->n_expose == ps->size_expose) { + if (ps->expose_rects) { + ps->expose_rects = + crealloc(ps->expose_rects, ps->size_expose + more); + ps->size_expose += more; + } else { + ps->expose_rects = ccalloc(more, rect_t); + ps->size_expose = more; + } + } + + ps->expose_rects[ps->n_expose].x1 = ev->x; + ps->expose_rects[ps->n_expose].y1 = ev->y; + ps->expose_rects[ps->n_expose].x2 = ev->x + ev->width; + ps->expose_rects[ps->n_expose].y2 = ev->y + ev->height; + ps->n_expose++; + + if (ev->count == 0) { + expose_root(ps, ps->expose_rects, ps->n_expose); + ps->n_expose = 0; + } + } +} + +static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) { + if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) { + // Print out changed atom + xcb_get_atom_name_reply_t *reply = + xcb_get_atom_name_reply(ps->c, xcb_get_atom_name(ps->c, ev->atom), NULL); + const char *name = "?"; + int name_len = 1; + if (reply) { + name = xcb_get_atom_name_name(reply); + name_len = xcb_get_atom_name_name_length(reply); + } + + log_debug("{ atom = %.*s }", name_len, name); + free(reply); + } + + if (ps->root == ev->window) { + if (ps->o.use_ewmh_active_win && + ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) { + // to update focus + ps->pending_updates = true; + } else { + // Destroy the root "image" if the wallpaper probably changed + if (x_is_root_back_pixmap_atom(ps, ev->atom)) { + root_damaged(ps); + } + } + + // Unconcerned about any other proprties on root window + return; + } + + // If WM_STATE changes + if (ev->atom == ps->atoms->aWM_STATE) { + // Check whether it could be a client window + if (!find_toplevel(ps, ev->window)) { + // Reset event mask anyway + xcb_change_window_attributes(ps->c, ev->window, XCB_CW_EVENT_MASK, + (const uint32_t[]){determine_evmask( + ps, ev->window, WIN_EVMODE_UNKNOWN)}); + + auto w_top = find_toplevel2(ps, ev->window); + // Initialize client_win as early as possible + if (w_top && + (!w_top->client_win || w_top->client_win == w_top->base.id) && + wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) { + w_top->wmwin = false; + win_unmark_client(ps, w_top); + win_mark_client(ps, w_top, ev->window); + } + } + } + + // If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but + // there are always some stupid applications. (#144) + if (ev->atom == ps->atoms->a_NET_WM_WINDOW_TYPE) { + struct managed_win *w = NULL; + if ((w = find_toplevel(ps, ev->window))) + win_update_wintype(ps, w); + } + + // If _NET_WM_OPACITY changes + if (ev->atom == ps->atoms->a_NET_WM_WINDOW_OPACITY) { + auto w = find_managed_win(ps, ev->window) ?: find_toplevel(ps, ev->window); + if (w) { + win_update_opacity_prop(ps, w); + // we cannot receive OPACITY change when window is destroyed + assert(w->state != WSTATE_DESTROYING); + if (w->state == WSTATE_MAPPED) { + // See the winstate_t transition table + w->state = WSTATE_FADING; + } + w->opacity_tgt = win_calc_opacity_target(ps, w); + } + } + + // If frame extents property changes + if (ps->o.frame_opacity > 0 && ev->atom == ps->atoms->a_NET_FRAME_EXTENTS) { + auto w = find_toplevel(ps, ev->window); + if (w) { + win_update_frame_extents(ps, w, ev->window); + // If frame extents change, the window needs repaint + add_damage_from_win(ps, w); + } + } + + // If name changes + if (ps->o.track_wdata && + (ps->atoms->aWM_NAME == ev->atom || ps->atoms->a_NET_WM_NAME == ev->atom)) { + auto w = find_toplevel(ps, ev->window); + if (w && 1 == win_get_name(ps, w)) { + win_on_factor_change(ps, w); + } + } + + // If class changes + if (ps->o.track_wdata && ps->atoms->aWM_CLASS == ev->atom) { + auto w = find_toplevel(ps, ev->window); + if (w) { + win_get_class(ps, w); + win_on_factor_change(ps, w); + } + } + + // If role changes + if (ps->o.track_wdata && ps->atoms->aWM_WINDOW_ROLE== ev->atom) { + auto w = find_toplevel(ps, ev->window); + if (w && 1 == win_get_role(ps, w)) { + win_on_factor_change(ps, w); + } + } + + // If _COMPTON_SHADOW changes + if (ps->o.respect_prop_shadow && ps->atoms->a_COMPTON_SHADOW == ev->atom) { + auto w = find_managed_win(ps, ev->window); + if (w) { + win_update_prop_shadow(ps, w); + } + } + + // If a leader property changes + if ((ps->o.detect_transient && ps->atoms->aWM_TRANSIENT_FOR == ev->atom) || + (ps->o.detect_client_leader && ps->atoms->aWM_CLIENT_LEADER == ev->atom)) { + auto w = find_toplevel(ps, ev->window); + if (w) { + win_update_leader(ps, w); + } + } + + // Check for other atoms we are tracking + for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) { + if (platom->atom == ev->atom) { + auto w = find_managed_win(ps, ev->window); + if (!w) + w = find_toplevel(ps, ev->window); + if (w) + win_on_factor_change(ps, w); + break; + } + } +} + +static inline void repair_win(session_t *ps, struct managed_win *w) { + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) + return; + + region_t parts; + pixman_region32_init(&parts); + + if (!w->ever_damaged) { + win_extents(w, &parts); + set_ignore_cookie( + ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, XCB_NONE)); + } else { + xcb_xfixes_region_t tmp = x_new_id(ps->c); + xcb_xfixes_create_region(ps->c, tmp, 0, NULL); + set_ignore_cookie(ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, tmp)); + x_fetch_region(ps->c, tmp, &parts); + xcb_xfixes_destroy_region(ps->c, tmp); + pixman_region32_translate(&parts, w->g.x + w->g.border_width, + w->g.y + w->g.border_width); + } + + w->ever_damaged = true; + w->pixmap_damaged = true; + + // Why care about damage when screen is unredirected? + // We will force full-screen repaint on redirection. + if (!ps->redirected) { + pixman_region32_fini(&parts); + return; + } + + // Remove the part in the damage area that could be ignored + if (w->reg_ignore && win_is_region_ignore_valid(ps, w)) + pixman_region32_subtract(&parts, &parts, w->reg_ignore); + + add_damage(ps, &parts); + pixman_region32_fini(&parts); +} + +static inline void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de) { + /* + if (ps->root == de->drawable) { + root_damaged(); + return; + } */ + + auto w = find_managed_win(ps, de->drawable); + + if (!w) { + return; + } + + repair_win(ps, w); +} + +static inline void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) { + auto w = find_managed_win(ps, ev->affected_window); + if (!w || w->a.map_state == XCB_MAP_STATE_UNMAPPED) { + return; + } + + /* + * Empty bounding_shape may indicated an + * unmapped/destroyed window, in which case + * seemingly BadRegion errors would be triggered + * if we attempt to rebuild border_size + */ + // Mark the old border_size as damaged + region_t tmp = win_get_bounding_shape_global_by_val(w); + add_damage(ps, &tmp); + pixman_region32_fini(&tmp); + + win_update_bounding_shape(ps, w); + + // Mark the new border_size as damaged + tmp = win_get_bounding_shape_global_by_val(w); + add_damage(ps, &tmp); + pixman_region32_fini(&tmp); + + w->reg_ignore_valid = false; +} + +static inline void +ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) { + // The only selection we own is the _NET_WM_CM_Sn selection. + // If we lose that one, we should exit. + log_fatal("Another composite manager started and took the _NET_WM_CM_Sn " + "selection."); + exit(1); +} + +void ev_handle(session_t *ps, xcb_generic_event_t *ev) { + if ((ev->response_type & 0x7f) != KeymapNotify) { + discard_ignore(ps, ev->full_sequence); + } + + xcb_window_t wid = ev_window(ps, ev); + if (ev->response_type != ps->damage_event + XCB_DAMAGE_NOTIFY) { + log_debug("event %10.10s serial %#010x window %#010x \"%s\"", + ev_name(ps, ev), ev->full_sequence, wid, ev_window_name(ps, wid)); + } else { + log_trace("event %10.10s serial %#010x window %#010x \"%s\"", + ev_name(ps, ev), ev->full_sequence, wid, ev_window_name(ps, wid)); + } + + // Check if a custom XEvent constructor was registered in xlib for this event + // type, and call it discarding the constructed XEvent if any. XESetWireToEvent + // might be used by libraries to intercept messages from the X server e.g. the + // OpenGL lib waiting for DRI2 events. + + // XXX This exists to workaround compton issue #33, #34, #47 + // For even more details, see: + // https://bugs.freedesktop.org/show_bug.cgi?id=35945 + // https://lists.freedesktop.org/archives/xcb/2011-November/007337.html + auto proc = XESetWireToEvent(ps->dpy, ev->response_type, 0); + if (proc) { + XESetWireToEvent(ps->dpy, ev->response_type, proc); + XEvent dummy; + + // Stop Xlib from complaining about lost sequence numbers. + // proc might also just be Xlib internal event processing functions, and + // because they probably won't see all X replies, they will complain about + // missing sequence numbers. + // + // We only need the low 16 bits + ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff); + proc(ps->dpy, &dummy, (xEvent *)ev); + } + + // XXX redraw needs to be more fine grained + queue_redraw(ps); + + switch (ev->response_type) { + case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break; + case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break; + case CreateNotify: ev_create_notify(ps, (xcb_create_notify_event_t *)ev); break; + case ConfigureNotify: + ev_configure_notify(ps, (xcb_configure_notify_event_t *)ev); + break; + case DestroyNotify: + ev_destroy_notify(ps, (xcb_destroy_notify_event_t *)ev); + break; + case MapNotify: ev_map_notify(ps, (xcb_map_notify_event_t *)ev); break; + case UnmapNotify: ev_unmap_notify(ps, (xcb_unmap_notify_event_t *)ev); break; + case ReparentNotify: + ev_reparent_notify(ps, (xcb_reparent_notify_event_t *)ev); + break; + case CirculateNotify: + ev_circulate_notify(ps, (xcb_circulate_notify_event_t *)ev); + break; + case Expose: ev_expose(ps, (xcb_expose_event_t *)ev); break; + case PropertyNotify: + ev_property_notify(ps, (xcb_property_notify_event_t *)ev); + break; + case SelectionClear: + ev_selection_clear(ps, (xcb_selection_clear_event_t *)ev); + break; + case 0: ev_xcb_error(ps, (xcb_generic_error_t *)ev); break; + default: + if (ps->shape_exists && ev->response_type == ps->shape_event) { + ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev); + break; + } + if (ps->randr_exists && + ev->response_type == (ps->randr_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY)) { + set_root_flags(ps, ROOT_FLAGS_SCREEN_CHANGE); + break; + } + if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) { + ev_damage_notify(ps, (xcb_damage_notify_event_t *)ev); + break; + } + } +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000000..629dec0f3f --- /dev/null +++ b/src/event.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2019, Yuxuan Shui + +#include + +#include "common.h" + +void ev_handle(session_t *ps, xcb_generic_event_t *ev); diff --git a/src/kernel.c b/src/kernel.c index 07d810c7c1..51510452ba 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -6,6 +6,7 @@ #include "compiler.h" #include "kernel.h" +#include "log.h" #include "utils.h" /// Sum a region convolution kernel. Region is defined by a width x height rectangle whose @@ -14,20 +15,10 @@ double sum_kernel(const conv *map, int x, int y, int width, int height) { double ret = 0; // Compute sum of values which are "in range" - int xstart = x, xend = width + x; - if (xstart < 0) { - xstart = 0; - } - if (xend > map->w) { - xend = map->w; - } - int ystart = y, yend = height + y; - if (ystart < 0) { - ystart = 0; - } - if (yend > map->h) { - yend = map->h; - } + int xstart = normalize_i_range(x, 0, map->w), + xend = normalize_i_range(width + x, 0, map->w); + int ystart = normalize_i_range(y, 0, map->h), + yend = normalize_i_range(height + y, 0, map->h); assert(yend >= ystart && xend >= xstart); int d = map->w; @@ -59,7 +50,7 @@ double sum_kernel_normalized(const conv *map, int x, int y, int width, int heigh return ret; } -static double attr_const gaussian(double r, double x, double y) { +static inline double attr_const gaussian(double r, double x, double y) { // Formula can be found here: // https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics // Except a special case for r == 0 to produce sharp shadows @@ -68,13 +59,13 @@ static double attr_const gaussian(double r, double x, double y) { return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r); } -conv *gaussian_kernel(double r) { +conv *gaussian_kernel(double r, int size) { conv *c; - int size = r * 2 + 1; int center = size / 2; double t; + assert(size % 2 == 1); - c = cvalloc(sizeof(conv) + size * size * sizeof(double)); + c = cvalloc(sizeof(conv) + (size_t)(size * size) * sizeof(double)); c->w = c->h = size; c->rsum = NULL; t = 0.0; @@ -96,6 +87,51 @@ conv *gaussian_kernel(double r) { return c; } +/// Estimate the element of the sum of the first row in a gaussian kernel with standard +/// deviation `r` and size `size`, +static inline double estimate_first_row_sum(double size, double r) { + double factor = erf(size / r / sqrt(2)); + double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r; + return a / factor; +} + +/// Pick a suitable gaussian kernel radius for a given kernel size. The returned radius +/// is the maximum possible radius (<= size*2) that satisfies no sum of the rows in +/// the kernel are less than `row_limit` (up to certain precision). +static inline double gaussian_kernel_std_for_size(int size, double row_limit) { + assert(size > 0); + if (row_limit >= 1.0 / 2.0 / size) { + return size * 2; + } + double l = 0, r = size * 2; + while (r - l > 1e-2) { + double mid = (l + r) / 2.0; + double vmid = estimate_first_row_sum(size, mid); + if (vmid > row_limit) { + r = mid; + } else { + l = mid; + } + } + return (l + r) / 2.0; +} + +/// Create a gaussian kernel with auto detected standard deviation. The choosen standard +/// deviation tries to make sure the outer most pixels of the shadow are completely +/// transparent, so the transition from shadow to the background is smooth. +/// +/// @param[in] shadow_radius the radius of the shadow +conv *gaussian_kernel_autodetect_deviation(int shadow_radius) { + assert(shadow_radius >= 0); + int size = shadow_radius * 2 + 1; + + if (shadow_radius == 0) { + return gaussian_kernel(0, size); + } + double std = gaussian_kernel_std_for_size(shadow_radius, 1.0 / 256.0); + return gaussian_kernel(std, size); +} + /// preprocess kernels to make shadow generation faster /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive void sum_kernel_preprocess(conv *map) { diff --git a/src/kernel.h b/src/kernel.h index 4e96a7502a..251d127d0f 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -18,8 +18,16 @@ typedef struct conv { double attr_pure sum_kernel(const conv *map, int x, int y, int width, int height); double attr_pure sum_kernel_normalized(const conv *map, int x, int y, int width, int height); -/// Create a kernel with gaussian distribution of radius r -conv *gaussian_kernel(double r); +/// Create a kernel with gaussian distribution with standard deviation `r`, and size +/// `size`. +conv *gaussian_kernel(double r, int size); + +/// Create a gaussian kernel with auto detected standard deviation. The choosen standard +/// deviation tries to make sure the outer most pixels of the shadow are completely +/// transparent. +/// +/// @param[in] shadow_radius the radius of the shadow +conv *gaussian_kernel_autodetect_deviation(int shadow_radius); /// preprocess kernels to make shadow generation faster /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000000..19e2c2c277 --- /dev/null +++ b/src/list.h @@ -0,0 +1,108 @@ +#pragma once +#include +#include + +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) \ + ({ \ + const __typeof__(((type *)0)->member) *__mptr = (ptr); \ + (type *)((char *)__mptr - offsetof(type, member)); \ + }) + +struct list_node { + struct list_node *next, *prev; +}; + +#define list_entry(ptr, type, node) container_of(ptr, type, node) +#define list_next_entry(ptr, node) list_entry((ptr)->node.next, __typeof__(*(ptr)), node) +#define list_prev_entry(ptr, node) list_entry((ptr)->node.prev, __typeof__(*(ptr)), node) + +/// Insert a new node between two adjacent nodes in the list +static inline void __list_insert_between(struct list_node *prev, struct list_node *next, + struct list_node *new_) { + new_->prev = prev; + new_->next = next; + next->prev = new_; + prev->next = new_; +} + +/// Insert a new node after `curr` +static inline void list_insert_after(struct list_node *curr, struct list_node *new_) { + __list_insert_between(curr, curr->next, new_); +} + +/// Insert a new node before `curr` +static inline void list_insert_before(struct list_node *curr, struct list_node *new_) { + __list_insert_between(curr->prev, curr, new_); +} + +/// Link two nodes in the list, so `next` becomes the successor node of `prev` +static inline void __list_link(struct list_node *prev, struct list_node *next) { + next->prev = prev; + prev->next = next; +} + +/// Remove a node from the list +static inline void list_remove(struct list_node *to_remove) { + __list_link(to_remove->prev, to_remove->next); + to_remove->prev = (void *)-1; + to_remove->next = (void *)-2; +} + +/// Move `to_move` so that it's before `new_next` +static inline void list_move_before(struct list_node *to_move, struct list_node *new_next) { + list_remove(to_move); + list_insert_before(new_next, to_move); +} + +/// Move `to_move` so that it's after `new_prev` +static inline void list_move_after(struct list_node *to_move, struct list_node *new_prev) { + list_remove(to_move); + list_insert_after(new_prev, to_move); +} + +/// Initialize a list node that's intended to be the head node +static inline void list_init_head(struct list_node *head) { + head->next = head->prev = head; +} + +/// Replace list node `old` with `n` +static inline void list_replace(struct list_node *old, struct list_node *n) { + __list_insert_between(old->prev, old->next, n); + old->prev = (void *)-1; + old->next = (void *)-2; +} + +/// Return true if head is the only node in the list. Under usual circumstances this means +/// the list is empty +static inline bool list_is_empty(const struct list_node *head) { + return head->prev == head; +} + +/// Return true if `to_check` is the first node in list headed by `head` +static inline bool +list_node_is_first(const struct list_node *head, const struct list_node *to_check) { + return head->next == to_check; +} + +/// Return true if `to_check` is the last node in list headed by `head` +static inline bool +list_node_is_last(const struct list_node *head, const struct list_node *to_check) { + return head->prev == to_check; +} + +#define list_foreach(type, i, head, member) \ + for (type *i = list_entry((head)->next, type, member); &i->member != (head); \ + i = list_next_entry(i, member)) + +/// Like list_for_each, but it's safe to remove the current list node from the list +#define list_foreach_safe(type, i, head, member) \ + for (type *i = list_entry((head)->next, type, member), \ + *__tmp = list_next_entry(i, member); \ + &i->member != (head); i = __tmp, __tmp = list_next_entry(i, member)) diff --git a/src/log.c b/src/log.c index b23e30008f..7071d4c682 100644 --- a/src/log.c +++ b/src/log.c @@ -150,11 +150,13 @@ attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func, va_list args; va_start(args, fmt); - size_t blen = vasprintf(&buf, fmt, args); + int blen = vasprintf(&buf, fmt, args); va_end(args); - if (!buf) + if (blen < 0 || !buf) { + free(buf); return; + } struct timespec ts; timespec_get(&ts, TIME_UTC); @@ -163,9 +165,10 @@ attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func, strftime(time_buf, sizeof time_buf, "%x %T", tm); char *time = NULL; - size_t tlen = asprintf(&time, "%s.%03ld", time_buf, ts.tv_nsec / 1000000); - if (!time) { + int tlen = asprintf(&time, "%s.%03ld", time_buf, ts.tv_nsec / 1000000); + if (tlen < 0 || !time) { free(buf); + free(time); return; } @@ -190,7 +193,7 @@ attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func, head->ops->writev( head, (struct iovec[]){{.iov_base = "[ ", .iov_len = 2}, - {.iov_base = time, .iov_len = tlen}, + {.iov_base = time, .iov_len = (size_t)tlen}, {.iov_base = " ", .iov_len = 1}, {.iov_base = (void *)func, .iov_len = flen}, {.iov_base = " ", .iov_len = 1}, @@ -198,7 +201,7 @@ attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func, {.iov_base = (void *)log_level_str, .iov_len = llen}, {.iov_base = (void *)s, .iov_len = slen}, {.iov_base = " ] ", .iov_len = 3}, - {.iov_base = buf, .iov_len = blen}, + {.iov_base = buf, .iov_len = (size_t)blen}, {.iov_base = "\n", .iov_len = 1}}, 11); head = head->next; @@ -327,23 +330,23 @@ struct log_target *stderr_logger_new(void) { #ifdef CONFIG_OPENGL /// An opengl logger that can be used for logging into opengl debugging tools, /// such as apitrace -struct glx_string_marker_logger { +struct gl_string_marker_logger { struct log_target tgt; - void (*glx_string_marker)(GLsizei len, const char *); + PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker; }; -void glx_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) { - auto g = (struct glx_string_marker_logger *)tgt; - g->glx_string_marker(len, str); +void gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) { + auto g = (struct gl_string_marker_logger *)tgt; + g->gl_string_marker((GLsizei)len, str); } -static const struct log_ops glx_string_marker_logger_ops = { - .write = glx_string_marker_logger_write, +static const struct log_ops gl_string_marker_logger_ops = { + .write = gl_string_marker_logger_write, .writev = log_default_writev, .destroy = logger_trivial_destroy, }; -struct log_target *glx_string_marker_logger_new(void) { +struct log_target *gl_string_marker_logger_new(void) { if (!gl_has_extension("GL_GREMEDY_string_marker")) { return NULL; } @@ -352,14 +355,14 @@ struct log_target *glx_string_marker_logger_new(void) { if (!fnptr) return NULL; - auto ret = cmalloc(struct glx_string_marker_logger); - ret->tgt.ops = &glx_string_marker_logger_ops; - ret->glx_string_marker = fnptr; + auto ret = cmalloc(struct gl_string_marker_logger); + ret->tgt.ops = &gl_string_marker_logger_ops; + ret->gl_string_marker = fnptr; return &ret->tgt; } #else -struct log_target *glx_string_marker_logger_new(void) { +struct log_target *gl_string_marker_logger_new(void) { return NULL; } #endif diff --git a/src/log.h b/src/log.h index 4df3f8e11e..1d16b0d31b 100644 --- a/src/log.h +++ b/src/log.h @@ -89,6 +89,6 @@ static inline void log_deinit_tls(void) { attr_malloc struct log_target *stderr_logger_new(void); attr_malloc struct log_target *file_logger_new(const char *file); attr_malloc struct log_target *null_logger_new(void); -attr_malloc struct log_target *glx_string_marker_logger_new(void); +attr_malloc struct log_target *gl_string_marker_logger_new(void); // vim: set noet sw=8 ts=8: diff --git a/src/meson.build b/src/meson.build index 9d73bc3b95..644688fd5c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,12 +1,16 @@ +libev = dependency('libev', required: false) +if not libev.found() + libev = cc.find_library('ev') +endif base_deps = [ cc.find_library('m'), - cc.find_library('ev'), + libev, dependency('xcb', version: '>=1.9.2'), ] srcs = [ files('compton.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c', 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c', - 'options.c') ] + 'options.c', 'event.c', 'cache.c', 'atom.c') ] compton_inc = include_directories('.') cflags = [] @@ -23,6 +27,10 @@ foreach i : required_package base_deps += [dependency(i, required: true)] endforeach +if not cc.has_header('uthash.h') + error('Dependency uthash not found') +endif + deps = [] if get_option('config_file') @@ -62,8 +70,16 @@ if get_option('xrescheck') srcs += [ 'xrescheck.c' ] endif +if get_option('unittest') + cflags += ['-DUNIT_TEST'] +endif + subdir('backend') -executable('compton', srcs, c_args: cflags, - dependencies: [ base_deps, deps ], +compton = executable('compton', srcs, c_args: cflags, + dependencies: [ base_deps, deps, test_h_dep ], install: true, include_directories: compton_inc) + +if get_option('unittest') + test('compton unittest', compton, args: [ '--unittest' ]) +endif diff --git a/src/meta.h b/src/meta.h new file mode 100644 index 0000000000..4314356c32 --- /dev/null +++ b/src/meta.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2019, Yuxuan Shui + +#pragma once + +/// Macro metaprogramming + +#define _APPLY1(a, ...) a(__VA_ARGS__) +#define _APPLY2(a, ...) a(__VA_ARGS__) +#define _APPLY3(a, ...) a(__VA_ARGS__) +#define _APPLY4(a, ...) a(__VA_ARGS__) + +#define RIOTA1(x) x +#define RIOTA2(x) RIOTA1(x##1), RIOTA1(x##0) +#define RIOTA4(x) RIOTA2(x##1), RIOTA2(x##0) +#define RIOTA8(x) RIOTA4(x##1), RIOTA4(x##0) +#define RIOTA16(x) RIOTA8(x##1), RIOTA8(x##0) +/// Generate a list containing 31, 30, ..., 0, in binary +#define RIOTA32(x) RIOTA16(x##1), RIOTA16(x##0) + +#define CONCAT2(a, b) a##b +#define CONCAT1(a, b) CONCAT2(a, b) +#define CONCAT(a, b) CONCAT1(a, b) + +#define _ARGS_HEAD(head, ...) head +#define _ARGS_SKIP4(_1, _2, _3, _4, ...) __VA_ARGS__ +#define _ARGS_SKIP8(...) _APPLY1(_ARGS_SKIP4, _ARGS_SKIP4(__VA_ARGS__)) +#define _ARGS_SKIP16(...) _APPLY2(_ARGS_SKIP8, _ARGS_SKIP8(__VA_ARGS__)) +#define _ARGS_SKIP32(...) _APPLY3(_ARGS_SKIP16, _ARGS_SKIP16(__VA_ARGS__)) + +/// Return the 33rd argument +#define _ARG33(...) _APPLY4(_ARGS_HEAD, _ARGS_SKIP32(__VA_ARGS__)) + +/// Return the number of arguments passed in binary, handles at most 31 elements +#define VA_ARGS_LENGTH(...) _ARG33(0, ##__VA_ARGS__, RIOTA32(0)) + +#define LIST_APPLY_000000(fn, sep, ...) +#define LIST_APPLY_000001(fn, sep, x, ...) fn(x) +#define LIST_APPLY_000010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000001(fn, sep, __VA_ARGS__) +#define LIST_APPLY_000011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000010(fn, sep, __VA_ARGS__) +#define LIST_APPLY_000100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000011(fn, sep, __VA_ARGS__) +#define LIST_APPLY_000101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000100(fn, sep, __VA_ARGS__) +#define LIST_APPLY_000110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000101(fn, sep, __VA_ARGS__) +#define LIST_APPLY_000111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000110(fn, sep, __VA_ARGS__) +#define LIST_APPLY_001000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000111(fn, sep, __VA_ARGS__) +#define LIST_APPLY_001001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001000(fn, sep, __VA_ARGS__) +#define LIST_APPLY_001010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001001(fn, sep, __VA_ARGS__) +#define LIST_APPLY_001011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001010(fn, sep, __VA_ARGS__) +#define LIST_APPLY_001100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001011(fn, sep, __VA_ARGS__) +#define LIST_APPLY_001101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001100(fn, sep, __VA_ARGS__) +#define LIST_APPLY_001110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001101(fn, sep, __VA_ARGS__) +#define LIST_APPLY_001111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001110(fn, sep, __VA_ARGS__) +#define LIST_APPLY_010000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001111(fn, sep, __VA_ARGS__) +#define LIST_APPLY_010001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010000(fn, sep, __VA_ARGS__) +#define LIST_APPLY_010010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010001(fn, sep, __VA_ARGS__) +#define LIST_APPLY_010011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010010(fn, sep, __VA_ARGS__) +#define LIST_APPLY_010100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010011(fn, sep, __VA_ARGS__) +#define LIST_APPLY_010101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010100(fn, sep, __VA_ARGS__) +#define LIST_APPLY_010110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010101(fn, sep, __VA_ARGS__) +#define LIST_APPLY_010111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010110(fn, sep, __VA_ARGS__) +#define LIST_APPLY_011000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010111(fn, sep, __VA_ARGS__) +#define LIST_APPLY_011001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011000(fn, sep, __VA_ARGS__) +#define LIST_APPLY_011010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011001(fn, sep, __VA_ARGS__) +#define LIST_APPLY_011011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011010(fn, sep, __VA_ARGS__) +#define LIST_APPLY_011100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011011(fn, sep, __VA_ARGS__) +#define LIST_APPLY_011101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011100(fn, sep, __VA_ARGS__) +#define LIST_APPLY_011110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011101(fn, sep, __VA_ARGS__) +#define LIST_APPLY_011111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011110(fn, sep, __VA_ARGS__) +#define LIST_APPLY_(N, fn, sep, ...) CONCAT(LIST_APPLY_, N)(fn, sep, __VA_ARGS__) +#define LIST_APPLY(fn, sep, ...) \ + LIST_APPLY_(VA_ARGS_LENGTH(__VA_ARGS__), fn, sep, __VA_ARGS__) + +#define SEP_COMMA() , +#define SEP_COLON() ; +#define SEP_NONE() diff --git a/src/opengl.c b/src/opengl.c index b29ba72ca4..7e96af1fa6 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -24,11 +24,16 @@ #include "log.h" #include "region.h" #include "string_utils.h" +#include "uthash_extra.h" #include "utils.h" #include "win.h" #include "opengl.h" +#ifndef GL_TEXTURE_RECTANGLE +#define GL_TEXTURE_RECTANGLE 0x84F5 +#endif + static inline XVisualInfo *get_visualinfo_from_visual(session_t *ps, xcb_visualid_t visual) { XVisualInfo vreq = {.visualid = visual}; int nitems = 0; @@ -84,7 +89,10 @@ bool glx_init(session_t *ps, bool need_render) { ps->psglx = cmalloc(glx_session_t); memcpy(ps->psglx, &CGLX_SESSION_DEF, sizeof(glx_session_t)); - for (int i = 0; i < MAX_BLUR_PASS; ++i) { + // +1 for the zero terminator + ps->psglx->blur_passes = ccalloc(ps->o.blur_kernel_count, glx_blur_pass_t); + + for (int i = 0; i < ps->o.blur_kernel_count; ++i) { glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; ppass->unifm_factor_center = -1; ppass->unifm_offset_x = -1; @@ -192,7 +200,7 @@ bool glx_init(session_t *ps, bool need_render) { success = true; glx_init_end: - cxfree(pvis); + XFree(pvis); if (!success) glx_destroy(ps); @@ -220,17 +228,19 @@ void glx_destroy(session_t *ps) { return; // Free all GLX resources of windows - for (win *w = ps->list; w; w = w->next) + win_stack_foreach_managed(w, &ps->window_stack) { free_win_res_glx(ps, w); + } // Free GLSL shaders/programs - for (int i = 0; i < MAX_BLUR_PASS; ++i) { + for (int i = 0; i < ps->o.blur_kernel_count; ++i) { glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; if (ppass->frag_shader) glDeleteShader(ppass->frag_shader); if (ppass->prog) glDeleteProgram(ppass->prog); } + free(ps->psglx->blur_passes); glx_free_prog_main(ps, &ps->glx_prog_win); @@ -264,10 +274,12 @@ void glx_on_root_change(session_t *ps) { * Initialize GLX blur filter. */ bool glx_init_blur(session_t *ps) { + assert(ps->o.blur_kernel_count > 0); + assert(ps->o.blur_kerns); assert(ps->o.blur_kerns[0]); // Allocate PBO if more than one blur kernel is present - if (ps->o.blur_kerns[1]) { + if (ps->o.blur_kernel_count > 1) { // Try to generate a framebuffer GLuint fbo = 0; glGenFramebuffers(1, &fbo); @@ -318,50 +330,47 @@ bool glx_init_blur(session_t *ps) { extension = strdup(""); } - for (int i = 0; i < MAX_BLUR_PASS && ps->o.blur_kerns[i]; ++i) { + for (int i = 0; i < ps->o.blur_kernel_count; ++i) { auto kern = ps->o.blur_kerns[i]; glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; // Build shader - { - int width = kern->w, height = kern->h; - int nele = width * height - 1; - unsigned int len = - strlen(FRAG_SHADER_BLUR_PREFIX) + - strlen(sampler_type) + strlen(extension) + - (strlen(shader_add) + strlen(texture_func) + 42) * nele + - strlen(FRAG_SHADER_BLUR_SUFFIX) + - strlen(texture_func) + 12 + 1; - char *shader_str = ccalloc(len, char); - char *pc = shader_str; - sprintf(pc, FRAG_SHADER_BLUR_PREFIX, extension, sampler_type); - pc += strlen(pc); - assert(strlen(shader_str) < len); - - double sum = 0.0; - for (int j = 0; j < height; ++j) { - for (int k = 0; k < width; ++k) { - if (height / 2 == j && width / 2 == k) - continue; - double val = kern->data[j * width + k]; - if (val == 0) { - continue; - } - sum += val; - sprintf(pc, shader_add, val, texture_func, - k - width / 2, j - height / 2); - pc += strlen(pc); - assert(strlen(shader_str) < len); + int width = kern->w, height = kern->h; + int nele = width * height - 1; + assert(nele >= 0); + auto len = + strlen(FRAG_SHADER_BLUR_PREFIX) + strlen(sampler_type) + + strlen(extension) + + (strlen(shader_add) + strlen(texture_func) + 42) * (uint)nele + + strlen(FRAG_SHADER_BLUR_SUFFIX) + strlen(texture_func) + 12 + 1; + char *shader_str = ccalloc(len, char); + char *pc = shader_str; + sprintf(pc, FRAG_SHADER_BLUR_PREFIX, extension, sampler_type); + pc += strlen(pc); + assert(strlen(shader_str) < len); + + double sum = 0.0; + for (int j = 0; j < height; ++j) { + for (int k = 0; k < width; ++k) { + if (height / 2 == j && width / 2 == k) + continue; + double val = kern->data[j * width + k]; + if (val == 0) { + continue; } + sum += val; + sprintf(pc, shader_add, val, texture_func, + k - width / 2, j - height / 2); + pc += strlen(pc); + assert(strlen(shader_str) < len); } - - sprintf(pc, FRAG_SHADER_BLUR_SUFFIX, texture_func, sum); - assert(strlen(shader_str) < len); - ppass->frag_shader = - gl_create_shader(GL_FRAGMENT_SHADER, shader_str); - free(shader_str); } + sprintf(pc, FRAG_SHADER_BLUR_SUFFIX, texture_func, sum); + assert(strlen(shader_str) < len); + ppass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); + free(shader_str); + if (!ppass->frag_shader) { log_error("Failed to create fragment shader %d.", i); free(extension); @@ -443,8 +452,8 @@ bool glx_load_prog_main(session_t *ps, const char *vshader_str, const char *fsha /** * Bind a X pixmap to an OpenGL texture. */ -bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, unsigned width, - unsigned height, bool repeat, const struct glx_fbconfig_info *fbcfg) { +bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, + int height, bool repeat, const struct glx_fbconfig_info *fbcfg) { if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) return true; @@ -480,25 +489,27 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, } // Create GLX pixmap - unsigned depth = 0; + int depth = 0; if (!ptex->glpixmap) { need_release = false; // Retrieve pixmap parameters, if they aren't provided - if (!(width && height)) { - Window rroot = None; - int rx = 0, ry = 0; - unsigned rbdwid = 0; - if (!XGetGeometry(ps->dpy, pixmap, &rroot, &rx, &ry, &width, - &height, &rbdwid, &depth)) { + if (!width || !height) { + auto r = xcb_get_geometry_reply( + ps->c, xcb_get_geometry(ps->c, pixmap), NULL); + if (!r) { log_error("Failed to query info of pixmap %#010x.", pixmap); return false; } - if (depth > OPENGL_MAX_DEPTH) { + if (r->depth > OPENGL_MAX_DEPTH) { log_error("Requested depth %d higher than %d.", depth, OPENGL_MAX_DEPTH); return false; } + depth = r->depth; + width = r->width; + height = r->height; + free(r); } // Determine texture target, copied from compiz @@ -522,7 +533,7 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, GLX_TEXTURE_FORMAT_EXT, fbcfg->texture_fmt, GLX_TEXTURE_TARGET_EXT, - tex_tgt, + (GLint)tex_tgt, 0, }; @@ -636,7 +647,8 @@ void glx_set_clip(session_t *ps, const region_t *reg) { region_t reg_new; \ int nrects; \ const rect_t *rects; \ - pixman_region32_init_rect(®_new, dx, dy, width, height); \ + assert(width >= 0 && height >= 0); \ + pixman_region32_init_rect(®_new, dx, dy, (uint)width, (uint)height); \ pixman_region32_intersect(®_new, ®_new, (region_t *)reg_tgt); \ rects = pixman_region32_rectangles(®_new, &nrects); \ glBegin(GL_QUADS); \ @@ -682,7 +694,7 @@ static inline void glx_copy_region_to_tex(session_t *ps, GLenum tex_tgt, int bas bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc) { assert(ps->psglx->blur_passes[0].prog); - const bool more_passes = ps->psglx->blur_passes[1].prog; + const bool more_passes = ps->o.blur_kernel_count > 1; const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); const bool have_stencil = glIsEnabled(GL_STENCIL_TEST); bool ret = false; @@ -704,13 +716,13 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, inc_x += XFIXED_TO_DOUBLE(kern[0]) / 2; inc_y += XFIXED_TO_DOUBLE(kern[1]) / 2; } - inc_x = min_i(ps->o.resize_damage, inc_x); - inc_y = min_i(ps->o.resize_damage, inc_y); + inc_x = min2(ps->o.resize_damage, inc_x); + inc_y = min2(ps->o.resize_damage, inc_y); - mdx = max_i(dx - inc_x, 0); - mdy = max_i(dy - inc_y, 0); - int mdx2 = min_i(dx + width + inc_x, ps->root_width), - mdy2 = min_i(dy + height + inc_y, ps->root_height); + mdx = max2(dx - inc_x, 0); + mdy = max2(dy - inc_y, 0); + int mdx2 = min2(dx + width + inc_x, ps->root_width), + mdy2 = min2(dy + height + inc_y, ps->root_height); mwidth = mdx2 - mdx; mheight = mdy2 - mdy; } @@ -763,9 +775,9 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, // Texture scaling factor GLfloat texfac_x = 1.0f, texfac_y = 1.0f; - if (GL_TEXTURE_2D == tex_tgt) { - texfac_x /= mwidth; - texfac_y /= mheight; + if (tex_tgt == GL_TEXTURE_2D) { + texfac_x /= (GLfloat)mwidth; + texfac_y /= (GLfloat)mheight; } // Paint it back @@ -775,9 +787,8 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, } bool last_pass = false; - for (int i = 0; !last_pass; ++i) { - last_pass = !ps->psglx->blur_passes[i + 1].prog; - assert(i < MAX_BLUR_PASS - 1); + for (int i = 0; i < ps->o.blur_kernel_count; ++i) { + last_pass = (i == ps->o.blur_kernel_count - 1); const glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; assert(ppass->prog); @@ -816,39 +827,37 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, if (ppass->unifm_factor_center >= 0) glUniform1f(ppass->unifm_factor_center, factor_center); - { - P_PAINTREG_START(crect) { - const GLfloat rx = (crect.x1 - mdx) * texfac_x; - const GLfloat ry = (mheight - (crect.y1 - mdy)) * texfac_y; - const GLfloat rxe = rx + (crect.x2 - crect.x1) * texfac_x; - const GLfloat rye = ry - (crect.y2 - crect.y1) * texfac_y; - GLfloat rdx = crect.x1 - mdx; - GLfloat rdy = mheight - crect.y1 + mdy; - if (last_pass) { - rdx = crect.x1; - rdy = ps->root_height - crect.y1; - } - GLfloat rdxe = rdx + (crect.x2 - crect.x1); - GLfloat rdye = rdy - (crect.y2 - crect.y1); + P_PAINTREG_START(crect) { + auto rx = (GLfloat)(crect.x1 - mdx) * texfac_x; + auto ry = (GLfloat)(mheight - (crect.y1 - mdy)) * texfac_y; + auto rxe = rx + (GLfloat)(crect.x2 - crect.x1) * texfac_x; + auto rye = ry - (GLfloat)(crect.y2 - crect.y1) * texfac_y; + auto rdx = (GLfloat)(crect.x1 - mdx); + auto rdy = (GLfloat)(mheight - crect.y1 + mdy); + if (last_pass) { + rdx = (GLfloat)crect.x1; + rdy = (GLfloat)(ps->root_height - crect.y1); + } + auto rdxe = rdx + (GLfloat)(crect.x2 - crect.x1); + auto rdye = rdy - (GLfloat)(crect.y2 - crect.y1); - // log_trace("%f, %f, %f, %f -> %f, %f, %f, %f", rx, ry, - // rxe, rye, rdx, - // rdy, rdxe, rdye); + // log_trace("%f, %f, %f, %f -> %f, %f, %f, %f", rx, ry, + // rxe, rye, rdx, + // rdy, rdxe, rdye); - glTexCoord2f(rx, ry); - glVertex3f(rdx, rdy, z); + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); - glTexCoord2f(rxe, ry); - glVertex3f(rdxe, rdy, z); + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); - glTexCoord2f(rxe, rye); - glVertex3f(rdxe, rdye, z); + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); - glTexCoord2f(rx, rye); - glVertex3f(rdx, rdye, z); - } - P_PAINTREG_END(); + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); } + P_PAINTREG_END(); glUseProgram(0); @@ -880,7 +889,7 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, return ret; } -bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, float z, +bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, GLfloat factor, const region_t *reg_tgt) { // It's possible to dim in glx_render(), but it would be over-complicated // considering all those mess in color negation and modulation @@ -888,21 +897,19 @@ bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, float z, glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glColor4f(0.0f, 0.0f, 0.0f, factor); - { - P_PAINTREG_START(crect) { - // XXX what does all of these variables mean? - GLint rdx = crect.x1; - GLint rdy = ps->root_height - crect.y1; - GLint rdxe = rdx + (crect.x2 - crect.x1); - GLint rdye = rdy - (crect.y2 - crect.y1); - - glVertex3i(rdx, rdy, z); - glVertex3i(rdxe, rdy, z); - glVertex3i(rdxe, rdye, z); - glVertex3i(rdx, rdye, z); - } - P_PAINTREG_END(); + P_PAINTREG_START(crect) { + // XXX what does all of these variables mean? + GLint rdx = crect.x1; + GLint rdy = ps->root_height - crect.y1; + GLint rdxe = rdx + (crect.x2 - crect.x1); + GLint rdye = rdy - (crect.y2 - crect.y1); + + glVertex3i(rdx, rdy, z); + glVertex3i(rdxe, rdy, z); + glVertex3i(rdxe, rdye, z); + glVertex3i(rdx, rdye, z); } + P_PAINTREG_END(); glColor4f(0.0f, 0.0f, 0.0f, 0.0f); glDisable(GL_BLEND); @@ -941,7 +948,7 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, // This is all weird, but X Render is using premultiplied ARGB format, and // we need to use those things to correct it. Thanks to derhass for help. glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(opacity, opacity, opacity, opacity); + glColor4d(opacity, opacity, opacity, opacity); } if (!has_prog) { @@ -1023,7 +1030,7 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, assert(pprogram->prog); glUseProgram(pprogram->prog); if (pprogram->unifm_opacity >= 0) - glUniform1f(pprogram->unifm_opacity, opacity); + glUniform1f(pprogram->unifm_opacity, (float)opacity); if (pprogram->unifm_invert_color >= 0) glUniform1i(pprogram->unifm_invert_color, neg); if (pprogram->unifm_tex >= 0) @@ -1045,17 +1052,17 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, { P_PAINTREG_START(crect) { // XXX explain these variables - GLfloat rx = (double)(crect.x1 - dx + x); - GLfloat ry = (double)(crect.y1 - dy + y); - GLfloat rxe = rx + (double)(crect.x2 - crect.x1); - GLfloat rye = ry + (double)(crect.y2 - crect.y1); + auto rx = (GLfloat)(crect.x1 - dx + x); + auto ry = (GLfloat)(crect.y1 - dy + y); + auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); + auto rye = ry + (GLfloat)(crect.y2 - crect.y1); // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1] // [0-1] Thanks to amonakov for pointing out! if (GL_TEXTURE_2D == ptex->target) { - rx = rx / ptex->width; - ry = ry / ptex->height; - rxe = rxe / ptex->width; - rye = rye / ptex->height; + rx = rx / (GLfloat)ptex->width; + ry = ry / (GLfloat)ptex->height; + rxe = rxe / (GLfloat)ptex->width; + rye = rye / (GLfloat)ptex->height; } GLint rdx = crect.x1; GLint rdy = ps->root_height - crect.y1; @@ -1065,8 +1072,8 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, // Invert Y if needed, this may not work as expected, though. I // don't have such a FBConfig to test with. if (!ptex->y_inverted) { - ry = 1.0 - ry; - rye = 1.0 - rye; + ry = 1.0f - ry; + rye = 1.0f - rye; } // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", ri, rx, diff --git a/src/opengl.h b/src/opengl.h index d01294c60e..9008b8b288 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -26,7 +26,46 @@ #include #include -bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, float z, +typedef struct { + /// Fragment shader for blur. + GLuint frag_shader; + /// GLSL program for blur. + GLuint prog; + /// Location of uniform "offset_x" in blur GLSL program. + GLint unifm_offset_x; + /// Location of uniform "offset_y" in blur GLSL program. + GLint unifm_offset_y; + /// Location of uniform "factor_center" in blur GLSL program. + GLint unifm_factor_center; +} glx_blur_pass_t; + +/// Structure containing GLX-dependent data for a compton session. +typedef struct glx_session { + // === OpenGL related === + /// GLX context. + GLXContext context; + /// Whether we have GL_ARB_texture_non_power_of_two. + bool has_texture_non_power_of_two; + /// Current GLX Z value. + int z; + glx_blur_pass_t *blur_passes; +} glx_session_t; + +/// @brief Wrapper of a binded GLX texture. +typedef struct _glx_texture { + GLuint texture; + GLXPixmap glpixmap; + xcb_pixmap_t pixmap; + GLenum target; + int width; + int height; + bool y_inverted; +} glx_texture_t; + +#define CGLX_SESSION_INIT \ + { .context = NULL } + +bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, GLfloat factor, const region_t *reg_tgt); bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, @@ -46,8 +85,8 @@ bool glx_load_prog_main(session_t *ps, const char *vshader_str, const char *fsha glx_prog_main_t *pprogram); #endif -bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, unsigned width, - unsigned height, bool repeat, const struct glx_fbconfig_info *); +bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, + int height, bool repeat, const struct glx_fbconfig_info *); void glx_release_pixmap(session_t *ps, glx_texture_t *ptex); @@ -161,7 +200,7 @@ static inline void free_paint_glx(session_t *ps, paint_t *ppaint) { /** * Free GLX part of win. */ -static inline void free_win_res_glx(session_t *ps, win *w) { +static inline void free_win_res_glx(session_t *ps, struct managed_win *w) { free_paint_glx(ps, &w->paint); free_paint_glx(ps, &w->shadow_paint); #ifdef CONFIG_OPENGL diff --git a/src/options.c b/src/options.c index ff59e562b5..569df2061f 100644 --- a/src/options.c +++ b/src/options.c @@ -308,9 +308,14 @@ static void usage(int ret) { "--benchmark-wid window-id\n" " Specify window ID to repaint in benchmark mode. If omitted or is 0,\n" " the whole screen is repainted.\n" + "\n" "--monitor-repaint\n" " Highlight the updated area of the screen. For debugging the xrender\n" - " backend only.\n"; + " backend only.\n" + "\n" + "--debug-mode\n" + " Render into a separate window, and don't take over the screen. Useful\n" + " when you want to attach a debugger to compton\n"; FILE *f = (ret ? stderr : stdout); fputs(usage_text, f); #undef WARNING_DISABLED @@ -354,6 +359,7 @@ static const struct option longopts[] = { {"dbe", no_argument, NULL, 272}, {"paint-on-overlay", no_argument, NULL, 273}, {"sw-opti", no_argument, NULL, 274}, + {"vsync-aggressive", no_argument, NULL, 275}, {"use-ewmh-active-win", no_argument, NULL, 276}, {"respect-prop-shadow", no_argument, NULL, 277}, {"unredir-if-possible", no_argument, NULL, 278}, @@ -405,6 +411,7 @@ static const struct option longopts[] = { {"experimental-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, + {"debug-mode", no_argument, NULL, 802}, // Must terminate with a NULL entry {NULL, 0, NULL, 0}, }; @@ -482,7 +489,6 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, "open a bug report."; optind = 1; while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { - long val = 0; switch (o) { #define P_CASEBOOL(idx, option) \ case idx: \ @@ -490,9 +496,15 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, break #define P_CASELONG(idx, option) \ case idx: \ - if (!parse_long(optarg, &val)) \ + if (!parse_long(optarg, &opt->option)) { \ + exit(1); \ + } \ + break +#define P_CASEINT(idx, option) \ + case idx: \ + if (!parse_int(optarg, &opt->option)) { \ exit(1); \ - opt->option = val; \ + } \ break // clang-format off @@ -510,7 +522,7 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, case 320: // These options are handled by get_early_config() break; - P_CASELONG('D', fade_delta); + P_CASEINT('D', fade_delta); case 'I': opt->fade_in_step = normalize_d(atof(optarg)); break; case 'O': opt->fade_out_step = normalize_d(atof(optarg)); break; case 'c': shadow_enable = true; break; @@ -534,12 +546,12 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, case 'F': fading_enable = true; break; - P_CASELONG('r', shadow_radius); + P_CASEINT('r', shadow_radius); case 'o': opt->shadow_opacity = atof(optarg); break; - P_CASELONG('l', shadow_offset_x); - P_CASELONG('t', shadow_offset_y); + P_CASEINT('l', shadow_offset_x); + P_CASEINT('t', shadow_offset_y); case 'i': opt->inactive_opacity = normalize_d(atof(optarg)); break; @@ -586,7 +598,7 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEBOOL(266, shadow_ignore_shaped); P_CASEBOOL(267, detect_rounded_corners); P_CASEBOOL(268, detect_client_opacity); - P_CASELONG(269, refresh_rate); + P_CASEINT(269, refresh_rate); case 270: if (optarg) { opt->vsync = parse_vsync(optarg); @@ -624,7 +636,10 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEBOOL(280, inactive_dim_fixed); P_CASEBOOL(281, detect_transient); P_CASEBOOL(282, detect_client_leader); - P_CASEBOOL(283, blur_background); + case 283: + // --blur_background + opt->blur_method = BLUR_METHOD_KERNEL; + break; P_CASEBOOL(284, blur_background_frame); P_CASEBOOL(285, blur_background_fixed); P_CASEBOOL(286, dbus); @@ -655,10 +670,10 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, log_error("--glx-copy-from-front %s", deprecation_message); exit(1); break; - P_CASELONG(293, benchmark); + P_CASEINT(293, benchmark); case 294: // --benchmark-wid - opt->benchmark_wid = strtol(optarg, NULL, 0); + opt->benchmark_wid = (xcb_window_t)strtol(optarg, NULL, 0); break; case 295: log_error("--glx-use-copysubbuffermesa %s", deprecation_message); @@ -701,11 +716,13 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, break; case 301: // --blur-kern - if (!parse_blur_kern_lst(optarg, opt->blur_kerns, - MAX_BLUR_PASS, &conv_kern_hasneg)) + opt->blur_kerns = parse_blur_kern_lst(optarg, &conv_kern_hasneg, + &opt->blur_kernel_count); + if (!opt->blur_kerns) { exit(1); + } break; - P_CASELONG(302, resize_damage); + P_CASEINT(302, resize_damage); case 303: // --glx-use-gpushader4 log_warn("--glx-use-gpushader4 is deprecated since v6." @@ -766,6 +783,7 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEBOOL(733, experimental_backends); P_CASEBOOL(800, monitor_repaint); case 801: opt->print_diagnostics = true; break; + P_CASEBOOL(802, debug_mode); default: usage(1); break; #undef P_CASEBOOL } @@ -781,8 +799,8 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, } // Range checking and option assignments - opt->fade_delta = max_i(opt->fade_delta, 1); - opt->shadow_radius = max_i(opt->shadow_radius, 0); + opt->fade_delta = max2(opt->fade_delta, 1); + opt->shadow_radius = max2(opt->shadow_radius, 0); opt->shadow_red = normalize_d(opt->shadow_red); opt->shadow_green = normalize_d(opt->shadow_green); opt->shadow_blue = normalize_d(opt->shadow_blue); @@ -795,25 +813,24 @@ void get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, set_default_winopts(opt, winopt_mask, shadow_enable, fading_enable); // --blur-background-frame implies --blur-background - if (opt->blur_background_frame) - opt->blur_background = true; + if (opt->blur_background_frame && !opt->blur_method) { + opt->blur_method = BLUR_METHOD_KERNEL; + } // Other variables determined by options - // Determine whether we need to track focus changes - if (opt->inactive_opacity != opt->active_opacity || opt->inactive_dim) { - opt->track_focus = true; - } - // Determine whether we track window grouping if (opt->detect_transient || opt->detect_client_leader) { opt->track_leader = true; } // Fill default blur kernel - if (opt->blur_background && !opt->blur_kerns[0]) { - CHECK(parse_blur_kern_lst("3x3box", opt->blur_kerns, MAX_BLUR_PASS, - &conv_kern_hasneg)); + if (opt->blur_method == BLUR_METHOD_KERNEL && + (!opt->blur_kerns || !opt->blur_kerns[0])) { + opt->blur_kerns = parse_blur_kern_lst("3x3box", &conv_kern_hasneg, + &opt->blur_kernel_count); + CHECK(opt->blur_kerns); + CHECK(opt->blur_kernel_count); } if (opt->resize_damage < 0) { diff --git a/src/region.h b/src/region.h index 3e424d2f24..bda66e237c 100644 --- a/src/region.h +++ b/src/region.h @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2018 Yuxuan Shui #pragma once -#include -#include #include #include +#include +#include -#include "utils.h" #include "log.h" +#include "utils.h" typedef struct pixman_region32 pixman_region32_t; typedef struct pixman_box32 pixman_box32_t; @@ -16,38 +16,85 @@ typedef pixman_box32_t rect_t; RC_TYPE(region_t, rc_region, pixman_region32_init, pixman_region32_fini, static inline) -/// copy a region_t -static inline void -copy_region(region_t *dst, const region_t *p) { - pixman_region32_copy(dst, (region_t *)p); -} - -static inline void -dump_region(const region_t *x) { - int nrects; - const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects); - log_trace("nrects: %d", nrects); - for (int i = 0; i < nrects; i++) - log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2, rects[i].y2); +static inline void dump_region(const region_t *x) { + if (log_get_level_tls() < LOG_LEVEL_TRACE) { + return; + } + int nrects; + const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects); + log_trace("nrects: %d", nrects); + for (int i = 0; i < nrects; i++) + log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2, + rects[i].y2); } /// Convert one xcb rectangle to our rectangle type -static inline rect_t -from_x_rect(const xcb_rectangle_t *rect) { - return (rect_t) { - .x1 = rect->x, - .y1 = rect->y, - .x2 = rect->x + rect->width, - .y2 = rect->y + rect->height, - }; +static inline rect_t from_x_rect(const xcb_rectangle_t *rect) { + return (rect_t){ + .x1 = rect->x, + .y1 = rect->y, + .x2 = rect->x + rect->width, + .y2 = rect->y + rect->height, + }; } /// Convert an array of xcb rectangles to our rectangle type /// Returning an array that needs to be freed -static inline rect_t * -from_x_rects(int nrects, const xcb_rectangle_t *rects) { - rect_t *ret = ccalloc(nrects, rect_t); - for (int i = 0; i < nrects; i++) - ret[i] = from_x_rect(rects+i); - return ret; +static inline rect_t *from_x_rects(int nrects, const xcb_rectangle_t *rects) { + rect_t *ret = ccalloc(nrects, rect_t); + for (int i = 0; i < nrects; i++) { + ret[i] = from_x_rect(rects + i); + } + return ret; +} + +/** + * Resize a region. + */ +static inline void _resize_region(const region_t *region, region_t *output, int dx, + int dy) { + if (!region || !output) { + return; + } + if (!dx && !dy) { + if (region != output) { + pixman_region32_copy(output, (region_t *)region); + } + return; + } + // Loop through all rectangles + int nrects; + int nnewrects = 0; + const rect_t *rects = pixman_region32_rectangles((region_t *)region, &nrects); + auto newrects = ccalloc(nrects, rect_t); + for (int i = 0; i < nrects; i++) { + int x1 = rects[i].x1 - dx; + int y1 = rects[i].y1 - dy; + int x2 = rects[i].x2 + dx; + int y2 = rects[i].y2 + dy; + int wid = x2 - x1; + int hei = y2 - y1; + if (wid <= 0 || hei <= 0) { + continue; + } + newrects[nnewrects] = + (rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2}; + ++nnewrects; + } + + pixman_region32_fini(output); + pixman_region32_init_rects(output, newrects, nnewrects); + + free(newrects); +} + +static inline region_t resize_region(const region_t *region, int dx, int dy) { + region_t ret; + pixman_region32_init(&ret); + _resize_region(region, &ret, dx, dy); + return ret; +} + +static inline void resize_region_in_place(region_t *region, int dx, int dy) { + return _resize_region(region, region, dx, dy); } diff --git a/src/render.c b/src/render.c index a371456ec7..d82803a566 100644 --- a/src/render.c +++ b/src/render.c @@ -15,6 +15,11 @@ #ifdef CONFIG_OPENGL #include "backend/gl/glx.h" #include "opengl.h" + +#ifndef GLX_BACK_BUFFER_AGE_EXT +#define GLX_BACK_BUFFER_AGE_EXT 0x20F4 +#endif + #endif #include "compiler.h" @@ -31,12 +36,15 @@ #include "backend/backend_common.h" #include "render.h" +#define XRFILTER_CONVOLUTION "convolution" +#define XRFILTER_GAUSSIAN "gaussian" +#define XRFILTER_BINOMIAL "binomial" + /** * Bind texture in paint_t if we are using GLX backend. */ -static inline bool -paint_bind_tex(session_t *ps, paint_t *ppaint, unsigned wid, unsigned hei, bool repeat, - int depth, xcb_visualid_t visual, bool force) { +static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int hei, + bool repeat, int depth, xcb_visualid_t visual, bool force) { #ifdef CONFIG_OPENGL // XXX This is a mess. But this will go away after the backend refactor. static thread_local struct glx_fbconfig_info *argb_fbconfig = NULL; @@ -174,13 +182,15 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, doubl switch (ps->o.backend) { case BKEND_XRENDER: case BKEND_XR_GLX_HYBRID: { - int alpha_step = opacity * MAX_ALPHA; + auto alpha_step = (int)(opacity * MAX_ALPHA); xcb_render_picture_t alpha_pict = ps->alpha_picts[alpha_step]; if (alpha_step != 0) { - int op = ((!argb && !alpha_pict) ? XCB_RENDER_PICT_OP_SRC - : XCB_RENDER_PICT_OP_OVER); - xcb_render_composite(ps->c, op, pict, alpha_pict, ps->tgt_buffer.pict, - x, y, 0, 0, dx, dy, wid, hei); + uint8_t op = ((!argb && !alpha_pict) ? XCB_RENDER_PICT_OP_SRC + : XCB_RENDER_PICT_OP_OVER); + xcb_render_composite( + ps->c, op, pict, alpha_pict, ps->tgt_buffer.pict, + to_i16_checked(x), to_i16_checked(y), 0, 0, to_i16_checked(dx), + to_i16_checked(dy), to_u16_checked(wid), to_u16_checked(hei)); } break; } @@ -196,8 +206,8 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, doubl } static inline void -paint_region(session_t *ps, win *w, int x, int y, int wid, int hei, double opacity, - const region_t *reg_paint, xcb_render_picture_t pict) { +paint_region(session_t *ps, const struct managed_win *w, int x, int y, int wid, int hei, + double opacity, const region_t *reg_paint, xcb_render_picture_t pict) { const int dx = (w ? w->g.x : 0) + x; const int dy = (w ? w->g.y : 0) + y; const bool argb = (w && (win_has_alpha(w) || ps->o.force_win_blend)); @@ -236,19 +246,19 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) { /** * Paint a window itself and dim it if asked. */ -void paint_one(session_t *ps, win *w, const region_t *reg_paint) { +void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) { // Fetch Pixmap if (!w->paint.pixmap) { - w->paint.pixmap = xcb_generate_id(ps->c); - set_ignore_cookie( - ps, xcb_composite_name_window_pixmap(ps->c, w->id, w->paint.pixmap)); + w->paint.pixmap = x_new_id(ps->c); + set_ignore_cookie(ps, xcb_composite_name_window_pixmap(ps->c, w->base.id, + w->paint.pixmap)); } xcb_drawable_t draw = w->paint.pixmap; if (!draw) { log_error("Failed to get pixmap from window %#010x (%s), window won't be " "visible", - w->id, w->name); + w->base.id, w->name); return; } @@ -268,19 +278,19 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { // causing the jittering issue M4he reported in #7. if (!paint_bind_tex(ps, &w->paint, 0, 0, false, 0, w->a.visual, (!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) { - log_error("Failed to bind texture for window %#010x.", w->id); + log_error("Failed to bind texture for window %#010x.", w->base.id); } w->pixmap_damaged = false; if (!paint_isvalid(ps, &w->paint)) { - log_error("Window %#010x is missing painting data.", w->id); + log_error("Window %#010x is missing painting data.", w->base.id); return; } const int x = w->g.x; const int y = w->g.y; - const int wid = w->widthb; - const int hei = w->heightb; + const uint16_t wid = to_u16_checked(w->widthb); + const uint16_t hei = to_u16_checked(w->heightb); xcb_render_picture_t pict = w->paint.pict; @@ -320,10 +330,10 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { } else { // Painting parameters const margin_t extents = win_calc_frame_extents(w); - const int t = extents.top; - const int l = extents.left; - const int b = extents.bottom; - const int r = extents.right; + const auto t = extents.top; + const auto l = extents.left; + const auto b = extents.bottom; + const auto r = extents.right; #define COMP_BDR(cx, cy, cwid, chei) \ paint_region(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity * w->opacity, \ @@ -337,7 +347,7 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { int body_height = hei; // ctop = checked top // Make sure top margin is smaller than height - int ctop = min_i(body_height, t); + int ctop = min2(body_height, t); if (ctop > 0) COMP_BDR(0, 0, wid, ctop); @@ -348,7 +358,7 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { // bottom // cbot = checked bottom // Make sure bottom margin is not too large - int cbot = min_i(body_height, b); + int cbot = min2(body_height, b); if (cbot > 0) COMP_BDR(0, hei - cbot, wid, cbot); @@ -359,7 +369,7 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { // left int body_width = wid; - int cleft = min_i(body_width, l); + int cleft = min2(body_width, l); if (cleft > 0) COMP_BDR(0, ctop, cleft, body_height); @@ -368,7 +378,7 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { break; // right - int cright = min_i(body_width, r); + int cright = min2(body_width, r); if (cright > 0) COMP_BDR(wid - cright, ctop, cright, body_height); @@ -396,7 +406,7 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { switch (ps->o.backend) { case BKEND_XRENDER: case BKEND_XR_GLX_HYBRID: { - unsigned short cval = 0xffff * dim_opacity; + auto cval = (uint16_t)(0xffff * dim_opacity); // Premultiply color xcb_render_color_t color = { @@ -407,8 +417,8 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { }; xcb_rectangle_t rect = { - .x = x, - .y = y, + .x = to_i16_checked(x), + .y = to_i16_checked(y), .width = wid, .height = hei, }; @@ -418,8 +428,8 @@ void paint_one(session_t *ps, win *w, const region_t *reg_paint) { } break; #ifdef CONFIG_OPENGL case BKEND_GLX: - glx_dim_dst(ps, x, y, wid, hei, ps->psglx->z - 0.7, dim_opacity, - reg_paint); + glx_dim_dst(ps, x, y, wid, hei, (int)(ps->psglx->z - 0.7), + (float)dim_opacity, reg_paint); break; #endif default: assert(false); @@ -447,7 +457,7 @@ static bool get_root_tile(session_t *ps) { // Create a pixmap if there isn't any if (!pixmap) { - pixmap = x_create_pixmap(ps->c, ps->depth, ps->root, 1, 1); + pixmap = x_create_pixmap(ps->c, (uint8_t)ps->depth, ps->root, 1, 1); if (pixmap == XCB_NONE) { log_error("Failed to create pixmaps for root tile."); return false; @@ -503,7 +513,7 @@ static void paint_root(session_t *ps, const region_t *reg_paint) { /** * Generate shadow Picture for a window. */ -static bool win_build_shadow(session_t *ps, win *w, double opacity) { +static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacity) { const int width = w->widthb; const int height = w->heightb; // log_trace("(): building shadow for %s %d %d", w->name, width, height); @@ -536,7 +546,7 @@ static bool win_build_shadow(session_t *ps, win *w, double opacity) { if (!shadow_picture || !shadow_picture_argb) goto shadow_picture_err; - gc = xcb_generate_id(ps->c); + gc = x_new_id(ps->c); xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL); xcb_image_put(ps->c, shadow_pixmap, gc, shadow_image, 0, 0, 0); @@ -576,12 +586,13 @@ static bool win_build_shadow(session_t *ps, win *w, double opacity) { /** * Paint the shadow of a window. */ -static inline void win_paint_shadow(session_t *ps, win *w, region_t *reg_paint) { +static inline void +win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { // Bind shadow pixmap to GLX texture if needed paint_bind_tex(ps, &w->shadow_paint, 0, 0, false, 32, 0, false); if (!paint_isvalid(ps, &w->shadow_paint)) { - log_error("Window %#010x is missing shadow data.", w->id); + log_error("Window %#010x is missing shadow data.", w->base.id); return; } @@ -605,8 +616,10 @@ static inline void win_paint_shadow(session_t *ps, win *w, region_t *reg_paint) * * @return true if successful, false otherwise */ -static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int x, int y, int wid, - int hei, xcb_render_fixed_t **blur_kerns, const region_t *reg_clip) { +static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y, + uint16_t wid, uint16_t hei, struct x_convolution_kernel **blur_kerns, + int nkernels, const region_t *reg_clip) { + assert(blur_kerns); assert(blur_kerns[0]); // Directly copying from tgt_buffer to it does not work, so we create a @@ -623,20 +636,19 @@ static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int x, i x_set_picture_clip_region(ps->c, tmp_picture, 0, 0, reg_clip); xcb_render_picture_t src_pict = tgt_buffer, dst_pict = tmp_picture; - for (int i = 0; blur_kerns[i]; ++i) { - assert(i < MAX_BLUR_PASS - 1); - xcb_render_fixed_t *convolution_blur = blur_kerns[i]; + for (int i = 0; i < nkernels; ++i) { + xcb_render_fixed_t *convolution_blur = blur_kerns[i]->kernel; // `x / 65536.0` converts from X fixed point to double - int kwid = ((double)convolution_blur[0]) / 65536.0, - khei = ((double)convolution_blur[1]) / 65536.0; + int kwid = (int)((double)convolution_blur[0] / 65536.0), + khei = (int)((double)convolution_blur[1] / 65536.0); bool rd_from_tgt = (tgt_buffer == src_pict); // Copy from source picture to destination. The filter must // be applied on source picture, to get the nearby pixels outside the // window. - xcb_render_set_picture_filter(ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), - XRFILTER_CONVOLUTION, kwid * khei + 2, - convolution_blur); + xcb_render_set_picture_filter( + ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), XRFILTER_CONVOLUTION, + (uint32_t)(kwid * khei + 2), convolution_blur); xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, dst_pict, (rd_from_tgt ? x : 0), (rd_from_tgt ? y : 0), 0, 0, (rd_from_tgt ? 0 : x), @@ -662,12 +674,13 @@ static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int x, i /** * Blur the background of a window. */ -static inline void win_blur_background(session_t *ps, win *w, xcb_render_picture_t tgt_buffer, - const region_t *reg_paint) { - const int x = w->g.x; - const int y = w->g.y; - const int wid = w->widthb; - const int hei = w->heightb; +static inline void +win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t tgt_buffer, + const region_t *reg_paint) { + const int16_t x = w->g.x; + const int16_t y = w->g.y; + const auto wid = to_u16_checked(w->widthb); + const auto hei = to_u16_checked(w->heightb); double factor_center = 1.0; // Adjust blur strength according to window opacity, to make it appear @@ -681,36 +694,28 @@ static inline void win_blur_background(session_t *ps, win *w, xcb_render_picture case BKEND_XRENDER: case BKEND_XR_GLX_HYBRID: { // Normalize blur kernels - for (int i = 0; i < MAX_BLUR_PASS; ++i) { + for (int i = 0; i < ps->o.blur_kernel_count; i++) { // Note: `x * 65536` converts double `x` to a X fixed point // representation. `x / 65536` is the other way. auto kern_src = ps->o.blur_kerns[i]; - xcb_render_fixed_t *kern_dst = ps->blur_kerns_cache[i]; - assert(i < MAX_BLUR_PASS); - if (!kern_src) { - assert(!kern_dst); - break; - } + auto kern_dst = ps->blur_kerns_cache[i]; - assert(!kern_dst || (kern_src->w == kern_dst[0] / 65536 && - kern_src->h == kern_dst[1] / 65536)); + assert(!kern_dst || (kern_src->w == kern_dst->kernel[0] / 65536 && + kern_src->h == kern_dst->kernel[1] / 65536)); // Skip for fixed factor_center if the cache exists already if (ps->o.blur_background_fixed && kern_dst) { continue; } - // If kern_dst is allocated, it's always allocated to the right - // size - size_t size = kern_dst ? kern_src->w * kern_src->h + 2 : 0; - x_picture_filter_from_conv(kern_src, factor_center, &kern_dst, &size); - ps->blur_kerns_cache[i] = kern_dst; + x_create_convolution_kernel(kern_src, factor_center, + &ps->blur_kerns_cache[i]); } // Minimize the region we try to blur, if the window itself is not // opaque, only the frame is. region_t reg_blur = win_get_bounding_shape_global_by_val(w); - if (win_is_solid(ps, w)) { + if (w->mode == WMODE_FRAME_TRANS && !ps->o.force_win_blend) { region_t reg_noframe; pixman_region32_init(®_noframe); win_get_region_noframe_local(w, ®_noframe); @@ -720,62 +725,33 @@ static inline void win_blur_background(session_t *ps, win *w, xcb_render_picture } // Translate global coordinates to local ones pixman_region32_translate(®_blur, -x, -y); - xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, ®_blur); + xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, + ps->o.blur_kernel_count, ®_blur); pixman_region32_clear(®_blur); } break; #ifdef CONFIG_OPENGL case BKEND_GLX: // TODO: Handle frame opacity - glx_blur_dst(ps, x, y, wid, hei, ps->psglx->z - 0.5, factor_center, - reg_paint, &w->glx_blur_cache); + glx_blur_dst(ps, x, y, wid, hei, (float)ps->psglx->z - 0.5f, + (float)factor_center, reg_paint, &w->glx_blur_cache); break; #endif default: assert(0); } } -/** - * Resize a region. - */ -static inline void resize_region(region_t *region, short mod) { - if (!mod || !region) - return; - // Loop through all rectangles - int nrects; - int nnewrects = 0; - pixman_box32_t *rects = pixman_region32_rectangles(region, &nrects); - auto newrects = ccalloc(nrects, pixman_box32_t); - for (int i = 0; i < nrects; i++) { - int x1 = rects[i].x1 - mod; - int y1 = rects[i].y1 - mod; - int x2 = rects[i].x2 + mod; - int y2 = rects[i].y2 + mod; - int wid = x2 - x1; - int hei = y2 - y1; - if (wid <= 0 || hei <= 0) - continue; - newrects[nnewrects] = - (pixman_box32_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2}; - ++nnewrects; - } - - pixman_region32_fini(region); - pixman_region32_init_rects(region, newrects, nnewrects); - - free(newrects); -} - /// paint all windows /// region = ?? /// region_real = the damage region -void paint_all(session_t *ps, win *const t, bool ignore_damage) { - if (ps->o.xrender_sync_fence) { +void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { + if (ps->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) { if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) { log_error("x_fence_sync failed, xrender-sync-fence will be " "disabled from now on."); xcb_sync_destroy_fence(ps->c, ps->sync_fence); ps->sync_fence = XCB_NONE; ps->o.xrender_sync_fence = false; + ps->xsync_exists = false; } } @@ -786,7 +762,7 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { pixman_region32_copy(®ion, &ps->screen_reg); } else { for (int i = 0; i < get_buffer_age(ps); i++) { - const int curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage; + auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage; pixman_region32_union(®ion, ®ion, &ps->damage_ring[curr]); } } @@ -800,7 +776,7 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { #endif if (ps->o.resize_damage > 0) { - resize_region(®ion, ps->o.resize_damage); + resize_region_in_place(®ion, ps->o.resize_damage, ps->o.resize_damage); } // Remove the damaged area out of screen @@ -809,8 +785,9 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { if (!paint_isvalid(ps, &ps->tgt_buffer)) { if (!ps->tgt_buffer.pixmap) { free_paint(ps, &ps->tgt_buffer); - ps->tgt_buffer.pixmap = x_create_pixmap( - ps->c, ps->depth, ps->root, ps->root_width, ps->root_height); + ps->tgt_buffer.pixmap = + x_create_pixmap(ps->c, (uint8_t)ps->depth, ps->root, + ps->root_width, ps->root_height); if (ps->tgt_buffer.pixmap == XCB_NONE) { log_fatal("Failed to allocate a screen-sized pixmap for" "painting"); @@ -854,7 +831,7 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { // pixels painted. // // Whether this is beneficial is to be determined XXX - for (win *w = t; w; w = w->prev_trans) { + for (auto w = t; w; w = w->prev_trans) { region_t bshape = win_get_bounding_shape_global_by_val(w); // Painting shadow if (w->shadow) { @@ -874,9 +851,10 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { // Might be worth while to crop the region to shadow // border + assert(w->shadow_width >= 0 && w->shadow_height >= 0); pixman_region32_intersect_rect( - ®_tmp, ®_tmp, w->g.x + w->shadow_dx, - w->g.y + w->shadow_dy, w->shadow_width, w->shadow_height); + ®_tmp, ®_tmp, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, + (uint)w->shadow_width, (uint)w->shadow_height); // Mask out the body of the window from the shadow if // needed Doing it here instead of in make_shadow() for @@ -917,8 +895,9 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { set_tgt_clip(ps, ®_tmp); // Blur window background if (w->blur_background && - (!win_is_solid(ps, w) || - (ps->o.blur_background_frame && w->frame_opacity != 1))) + (w->mode == WMODE_TRANS || + (ps->o.blur_background_frame && w->mode == WMODE_FRAME_TRANS) || + ps->o.force_win_blend)) win_blur_background(ps, w, ps->tgt_buffer.pict, ®_tmp); // Painting the window @@ -958,6 +937,8 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { ps->vsync_wait(ps); } + auto rwidth = to_u16_checked(ps->root_width); + auto rheight = to_u16_checked(ps->root_height); switch (ps->o.backend) { case BKEND_XRENDER: if (ps->o.monitor_repaint) { @@ -969,16 +950,16 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { // to it auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis); xcb_render_picture_t new_pict = x_create_picture_with_pictfmt( - ps->c, ps->root, ps->root_width, ps->root_height, pictfmt, 0, NULL); + ps->c, ps->root, rwidth, rheight, pictfmt, 0, NULL); xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, - ps->tgt_buffer.pict, XCB_NONE, new_pict, 0, 0, - 0, 0, 0, 0, ps->root_width, ps->root_height); + ps->tgt_buffer.pict, XCB_NONE, new_pict, 0, + 0, 0, 0, 0, 0, rwidth, rheight); // Next, we set the region of paint and highlight it x_set_picture_clip_region(ps->c, new_pict, 0, 0, ®ion); xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, ps->white_picture, - ps->alpha_picts[MAX_ALPHA / 2], new_pict, 0, 0, - 0, 0, 0, 0, ps->root_width, ps->root_height); + ps->alpha_picts[MAX_ALPHA / 2], new_pict, 0, + 0, 0, 0, 0, 0, rwidth, rheight); // Finally, clear clip regions of new_pict and the screen, and put // the whole thing on screen @@ -986,12 +967,12 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { x_set_picture_clip_region(ps->c, ps->tgt_picture, 0, 0, &ps->screen_reg); xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, new_pict, XCB_NONE, ps->tgt_picture, 0, 0, 0, 0, 0, 0, - ps->root_width, ps->root_height); + rwidth, rheight); xcb_render_free_picture(ps->c, new_pict); } else - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, ps->tgt_buffer.pict, - XCB_NONE, ps->tgt_picture, 0, 0, 0, 0, 0, 0, - ps->root_width, ps->root_height); + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, + ps->tgt_buffer.pict, XCB_NONE, ps->tgt_picture, + 0, 0, 0, 0, 0, 0, rwidth, rheight); break; #ifdef CONFIG_OPENGL case BKEND_XR_GLX_HYBRID: @@ -1039,15 +1020,6 @@ void paint_all(session_t *ps, win *const t, bool ignore_damage) { // Free the paint region pixman_region32_fini(®ion); - - // Check if fading is finished on all painted windows - { - win *pprev = NULL; - for (win *w = t; w; w = pprev) { - pprev = w->prev_trans; - win_check_fade_finished(ps, &w); - } - } } /** @@ -1134,7 +1106,16 @@ bool init_render(session_t *ps) { } // Blur filter - if (ps->o.blur_background || ps->o.blur_background_frame) { + if (ps->o.blur_method && ps->o.blur_method != BLUR_METHOD_KERNEL) { + log_warn("Old backends only support blur method \"kernel\". Your blur " + "setting will not be applied"); + ps->o.blur_method = BLUR_METHOD_NONE; + } + + if (ps->o.blur_method == BLUR_METHOD_KERNEL) { + ps->blur_kerns_cache = + ccalloc(ps->o.blur_kernel_count, struct x_convolution_kernel *); + bool ret = false; if (ps->o.backend == BKEND_GLX) { #ifdef CONFIG_OPENGL @@ -1160,7 +1141,7 @@ bool init_render(session_t *ps) { // Generates another Picture for shadows if the color is modified by // user - if (!ps->o.shadow_red && !ps->o.shadow_green && !ps->o.shadow_blue) { + if (ps->o.shadow_red == 0 && ps->o.shadow_green == 0 && ps->o.shadow_blue == 0) { ps->cshadow_picture = ps->black_picture; } else { ps->cshadow_picture = solid_picture(ps->c, ps->root, true, 1, ps->o.shadow_red, @@ -1212,8 +1193,15 @@ void deinit_render(session_t *ps) { #ifdef CONFIG_OPENGL free(ps->root_tile_paint.fbcfg); - glx_destroy(ps); + if (bkend_use_glx(ps)) { + glx_destroy(ps); + } #endif + + for (int i = 0; i < ps->o.blur_kernel_count; i++) { + free(ps->blur_kerns_cache[i]); + } + free(ps->blur_kerns_cache); } // vim: set ts=8 sw=8 noet : diff --git a/src/render.h b/src/render.h index c05845ded7..92b71c8e01 100644 --- a/src/render.h +++ b/src/render.h @@ -2,9 +2,9 @@ // Copyright (c) Yuxuan Shui #pragma once -#include -#include #include +#include +#include #ifdef CONFIG_OPENGL #include "backend/gl/glx.h" #endif @@ -12,28 +12,25 @@ typedef struct _glx_texture glx_texture_t; typedef struct glx_prog_main glx_prog_main_t; -typedef struct win win; typedef struct session session_t; +struct managed_win; + typedef struct paint { - xcb_pixmap_t pixmap; - xcb_render_picture_t pict; - glx_texture_t *ptex; + xcb_pixmap_t pixmap; + xcb_render_picture_t pict; + glx_texture_t *ptex; #ifdef CONFIG_OPENGL - struct glx_fbconfig_info *fbcfg; + struct glx_fbconfig_info *fbcfg; #endif } paint_t; -void -render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, - double opacity, bool argb, bool neg, - xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram); -void -paint_one(session_t *ps, win *w, const region_t *reg_paint); +void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, double opacity, + bool argb, bool neg, xcb_render_picture_t pict, glx_texture_t *ptex, + const region_t *reg_paint, const glx_prog_main_t *pprogram); +void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); -void -paint_all(session_t *ps, win * const t, bool ignore_damage); +void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage); void free_picture(xcb_connection_t *c, xcb_render_picture_t *p); diff --git a/src/string_utils.c b/src/string_utils.c index 38eb92d8e8..65af0f209f 100644 --- a/src/string_utils.c +++ b/src/string_utils.c @@ -3,6 +3,8 @@ #include +#include + #include "compiler.h" #include "string_utils.h" #include "utils.h" @@ -33,6 +35,20 @@ char *mstrjoin(const char *src1, const char *src2) { return str; } +TEST_CASE(mstrjoin) { + char *str = mstrjoin("asdf", "qwer"); + TEST_STREQUAL(str, "asdfqwer"); + free(str); + + str = mstrjoin("", "qwer"); + TEST_STREQUAL(str, "qwer"); + free(str); + + str = mstrjoin("asdf", ""); + TEST_STREQUAL(str, "asdf"); + free(str); +} + /** * Concatenate a string on heap with another string. */ @@ -51,6 +67,19 @@ void mstrextend(char **psrc1, const char *src2) { (*psrc1)[len - 1] = '\0'; } +TEST_CASE(mstrextend) { + char *str1 = NULL; + mstrextend(&str1, "asdf"); + TEST_STREQUAL(str1, "asdf"); + + mstrextend(&str1, "asd"); + TEST_STREQUAL(str1, "asdfasd"); + + mstrextend(&str1, ""); + TEST_STREQUAL(str1, "asdfasd"); + free(str1); +} + #pragma GCC diagnostic pop /// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*) @@ -83,3 +112,18 @@ double strtod_simple(const char *src, const char **end) { *end = src; return ret * neg; } + +TEST_CASE(strtod_simple) { + const char *end; + double result = strtod_simple("1.0", &end); + TEST_EQUAL(result, 1); + TEST_EQUAL(*end, '\0'); + + result = strtod_simple("-1.0", &end); + TEST_EQUAL(result, -1); + TEST_EQUAL(*end, '\0'); + + result = strtod_simple("+.5", &end); + TEST_EQUAL(result, 0.5); + TEST_EQUAL(*end, '\0'); +} diff --git a/src/string_utils.h b/src/string_utils.h index 8f40bb124c..d49158d201 100644 --- a/src/string_utils.h +++ b/src/string_utils.h @@ -4,6 +4,8 @@ #include #include +#include "compiler.h" + #define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1)) char *mstrjoin(const char *src1, const char *src2); @@ -26,7 +28,7 @@ static inline int uitostr(unsigned int n, char *buf) { int pos = ret; while (pos--) { - buf[pos] = n % 10 + '0'; + buf[pos] = (char)(n % 10 + '0'); n /= 10; } return ret; diff --git a/src/types.h b/src/types.h index eb749f66a8..c8d747b6ee 100644 --- a/src/types.h +++ b/src/types.h @@ -14,22 +14,18 @@ typedef enum { UNSET } switch_t; -/// Structure representing a X geometry. -typedef struct { - int wid; - int hei; - int x; - int y; -} geometry_t; - /// A structure representing margins around a rectangle. typedef struct { - unsigned int top; - unsigned int left; - unsigned int bottom; - unsigned int right; + int top; + int left; + int bottom; + int right; } margin_t; +struct color { + double red, green, blue, alpha; +}; + typedef uint32_t opacity_t; #define MARGIN_INIT \ diff --git a/src/uthash_extra.h b/src/uthash_extra.h new file mode 100644 index 0000000000..cbc10561b7 --- /dev/null +++ b/src/uthash_extra.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#define HASH_ITER2(head, el) \ + for (__typeof__(head) el = (head), __tmp = el != NULL ? el->hh.next : NULL; \ + el != NULL; el = __tmp, __tmp = el != NULL ? el->hh.next : NULL) diff --git a/src/utils.c b/src/utils.c index d7e6b6f073..d863c363c7 100644 --- a/src/utils.c +++ b/src/utils.c @@ -22,7 +22,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int {.iov_base = "at ", .iov_len = 3}, {.iov_base = (void *)file, .iov_len = strlen(file)}, {.iov_base = ":", .iov_len = 1}, - {.iov_base = buf, .iov_len = llen}, + {.iov_base = buf, .iov_len = (size_t)llen}, {.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1}, }; diff --git a/src/utils.h b/src/utils.h index d6f6aa0234..374b7f37e1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -3,6 +3,7 @@ #pragma once #include #include +#include #include #include #include @@ -11,6 +12,8 @@ #include #include +#include + #include "compiler.h" #define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) @@ -27,9 +30,12 @@ __attribute__((optimize("-fno-fast-math"))) #endif static inline bool safe_isnan(double a) { - return isnan(a); + return __builtin_isnan(a); } +#define CASESTRRET(s) \ + case s: return #s + /// Same as assert false, but make sure we abort _even in release builds_. /// Silence compiler warning caused by release builds making some code paths reachable. #define BUG() \ @@ -37,15 +43,55 @@ safe_isnan(double a) { assert(false); \ abort(); \ } while (0) - +#define CHECK_EXPR(...) ((void)0) /// Same as assert, but evaluates the expression even in release builds -#define CHECK(expr) \ +#define CHECK(expr) \ do { \ - __auto_type __tmp = (expr); \ - assert(__tmp); \ - (void)__tmp; \ + __auto_type _ = (expr); \ + assert((CHECK_EXPR(expr), _)); \ + (void)_; \ } while (0) +// Some macros for checked cast +// Note these macros are not complete, as in, they won't work for every integer types. But +// they are good enough for compton. + +#define to_int_checked(val) \ + ({ \ + int64_t tmp = (val); \ + assert(tmp >= INT_MIN && tmp <= INT_MAX); \ + (int)tmp; \ + }) + +#define to_char_checked(val) \ + ({ \ + int64_t tmp = (val); \ + assert(tmp >= CHAR_MIN && tmp <= CHAR_MAX); \ + (char)tmp; \ + }) + +#define to_u16_checked(val) \ + ({ \ + auto tmp = (val); \ + assert(tmp >= 0 && tmp <= UINT16_MAX); \ + (uint16_t) tmp; \ + }) + +#define to_i16_checked(val) \ + ({ \ + int64_t tmp = (val); \ + assert(tmp >= INT16_MIN && tmp <= INT16_MAX); \ + (int16_t) tmp; \ + }) + +#define to_u32_checked(val) \ + ({ \ + auto tmp = (val); \ + int64_t max = UINT32_MAX; /* silence clang tautological \ + comparison warning*/ \ + CHECK(tmp >= 0 && tmp <= max); \ + (uint32_t) tmp; \ + }) /** * Normalize an int value to a specific range. * @@ -62,33 +108,11 @@ static inline int attr_const normalize_i_range(int i, int min, int max) { return i; } -/** - * Select the larger integer of two. - */ -static inline int attr_const max_i(int a, int b) { - return (a > b ? a : b); -} +#define min2(a, b) ((a) > (b) ? (b) : (a)) +#define max2(a, b) ((a) > (b) ? (a) : (b)) -/** - * Select the smaller integer of two. - */ -static inline int attr_const min_i(int a, int b) { - return (a > b ? b : a); -} - -/** - * Select the larger long integer of two. - */ -static inline long attr_const max_l(long a, long b) { - return (a > b ? a : b); -} - -/** - * Select the smaller long integer of two. - */ -static inline long attr_const min_l(long a, long b) { - return (a > b ? b : a); -} +/// clamp `val` into interval [min, max] +#define clamp(val, min, max) max2(min2(val, max), min) static inline int attr_const popcountl(unsigned long a) { return __builtin_popcountl(a); @@ -144,11 +168,20 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr) #define cvalloc(size) allocchk(malloc(size)) /// @brief Wrapper of calloc(). -#define ccalloc(nmemb, type) ((type *)allocchk(calloc((nmemb), sizeof(type)))) +#define ccalloc(nmemb, type) \ + ({ \ + auto tmp = (nmemb); \ + assert(tmp >= 0); \ + ((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \ + }) /// @brief Wrapper of ealloc(). -#define crealloc(ptr, nmemb) \ - ((__typeof__(ptr))allocchk(realloc((ptr), (nmemb) * sizeof(*(ptr))))) +#define crealloc(ptr, nmemb) \ + ({ \ + auto tmp = (nmemb); \ + assert(tmp >= 0); \ + ((__typeof__(ptr))allocchk(realloc((ptr), (size_t)tmp * sizeof(*(ptr))))); \ + }) /// RC_TYPE generates a reference counted type from `type` /// diff --git a/src/vsync.c b/src/vsync.c index fba4ebf82c..d5a5b8fb44 100644 --- a/src/vsync.c +++ b/src/vsync.c @@ -38,7 +38,7 @@ static int vsync_drm_wait(session_t *ps) { do { ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl); - vbl.request.type &= ~_DRM_VBLANK_RELATIVE; + vbl.request.type &= ~(uint)_DRM_VBLANK_RELATIVE; } while (ret && errno == EINTR); if (ret) @@ -90,9 +90,9 @@ static bool vsync_opengl_oml_init(session_t *ps) { return glxext.has_GLX_OML_sync_control; } -static inline bool vsync_opengl_swc_swap_interval(session_t *ps, unsigned int interval) { +static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) { if (glxext.has_GLX_MESA_swap_control) - return glXSwapIntervalMESA(interval) == 0; + return glXSwapIntervalMESA((uint)interval) == 0; else if (glxext.has_GLX_SGI_swap_control) return glXSwapIntervalSGI(interval) == 0; else if (glxext.has_GLX_EXT_swap_control) { diff --git a/src/win.c b/src/win.c index 4f5aa6bc94..e42ff9ba38 100644 --- a/src/win.c +++ b/src/win.c @@ -13,18 +13,22 @@ #include #include #include +#include +#include "atom.h" #include "backend/backend.h" #include "c2.h" #include "common.h" #include "compiler.h" #include "compton.h" #include "config.h" +#include "list.h" #include "log.h" #include "region.h" #include "render.h" #include "string_utils.h" #include "types.h" +#include "uthash_extra.h" #include "utils.h" #include "x.h" @@ -39,13 +43,16 @@ #include "win.h" -#define OPAQUE 0xffffffff +#define OPAQUE (0xffffffff) +static const int WIN_GET_LEADER_MAX_RECURSION = 20; +static const int ROUNDED_PIXELS = 1; +static const double ROUNDED_PERCENT = 0.05; /// Generate a "return by value" function, from a function that returns the /// region via a region_t pointer argument. /// Function signature has to be (win *, region_t *) #define gen_by_val(fun) \ - region_t fun##_by_val(const win *w) { \ + region_t fun##_by_val(const struct managed_win *w) { \ region_t ret; \ pixman_region32_init(&ret); \ fun(w, &ret); \ @@ -56,18 +63,19 @@ * Clear leader cache of all windows. */ static inline void clear_cache_win_leaders(session_t *ps) { - for (win *w = ps->list; w; w = w->next) + win_stack_foreach_managed(w, &ps->window_stack) { w->cache_leader = XCB_NONE; + } } static inline void wid_set_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t val) { const uint32_t v = val; - xcb_change_property(ps->c, XCB_PROP_MODE_REPLACE, wid, ps->atom_opacity, - XCB_ATOM_CARDINAL, 32, 1, &v); + xcb_change_property(ps->c, XCB_PROP_MODE_REPLACE, wid, + ps->atoms->a_NET_WM_WINDOW_OPACITY, XCB_ATOM_CARDINAL, 32, 1, &v); } static inline void wid_rm_opacity_prop(session_t *ps, xcb_window_t wid) { - xcb_delete_property(ps->c, wid, ps->atom_opacity); + xcb_delete_property(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_OPACITY); } /** @@ -79,9 +87,15 @@ static inline void group_update_focused(session_t *ps, xcb_window_t leader) { if (!leader) return; - for (win *w = ps->list; w; w = w->next) { - if (win_get_leader(ps, w) == leader && w->state != WSTATE_DESTROYING) - win_update_focused(ps, w); + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + if (!w->managed) { + continue; + } + auto mw = (struct managed_win *)w; + if (win_get_leader(ps, mw) == leader) { + win_update_focused(ps, mw); + } } return; @@ -94,13 +108,19 @@ static inline void group_update_focused(session_t *ps, xcb_window_t leader) { * @return true if the window group is focused, false otherwise */ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) { - if (!leader) + if (!leader) { return false; + } - for (win *w = ps->list; w; w = w->next) { - if (win_get_leader(ps, w) == leader && w->state != WSTATE_DESTROYING && - win_is_focused_real(ps, w)) + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + if (!w->managed) { + continue; + } + auto mw = (struct managed_win *)w; + if (win_get_leader(ps, mw) == leader && win_is_focused_real(ps, mw)) { return true; + } } return false; @@ -109,30 +129,32 @@ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) { /** * Get a rectangular region a window occupies, excluding shadow. */ -static void win_get_region_local(const win *w, region_t *res) { +static void win_get_region_local(const struct managed_win *w, region_t *res) { + assert(w->widthb >= 0 && w->heightb >= 0); pixman_region32_fini(res); - pixman_region32_init_rect(res, 0, 0, w->widthb, w->heightb); + pixman_region32_init_rect(res, 0, 0, (uint)w->widthb, (uint)w->heightb); } /** * Get a rectangular region a window occupies, excluding frame and shadow. */ -void win_get_region_noframe_local(const win *w, region_t *res) { +void win_get_region_noframe_local(const struct managed_win *w, region_t *res) { const margin_t extents = win_calc_frame_extents(w); int x = extents.left; int y = extents.top; - int width = max_i(w->g.width - extents.left - extents.right, 0); - int height = max_i(w->g.height - extents.top - extents.bottom, 0); + int width = max2(w->g.width - (extents.left + extents.right), 0); + int height = max2(w->g.height - (extents.top + extents.bottom), 0); pixman_region32_fini(res); - if (width > 0 && height > 0) - pixman_region32_init_rect(res, x, y, width, height); + if (width > 0 && height > 0) { + pixman_region32_init_rect(res, x, y, (uint)width, (uint)height); + } } gen_by_val(win_get_region_noframe_local); -void win_get_region_frame_local(const win *w, region_t *res) { +void win_get_region_frame_local(const struct managed_win *w, region_t *res) { const margin_t extents = win_calc_frame_extents(w); pixman_region32_fini(res); pixman_region32_init_rects( @@ -164,7 +186,7 @@ gen_by_val(win_get_region_frame_local); * @param ps current session * @param w struct _win element representing the window */ -void add_damage_from_win(session_t *ps, win *w) { +void add_damage_from_win(session_t *ps, const struct managed_win *w) { // XXX there was a cached extents region, investigate // if that's better region_t extents; @@ -174,11 +196,72 @@ void add_damage_from_win(session_t *ps, win *w) { pixman_region32_fini(&extents); } +/// Release the images attached to this window +void win_release_image(backend_t *base, struct managed_win *w) { + log_debug("Releasing image of window %#010x (%s)", w->base.id, w->name); + assert(w->win_image || (w->flags & WIN_FLAGS_IMAGE_ERROR)); + if (w->win_image) { + base->ops->release_image(base, w->win_image); + w->win_image = NULL; + } + if (w->shadow) { + assert(w->shadow_image || (w->flags & WIN_FLAGS_IMAGE_ERROR)); + if (w->shadow_image) { + base->ops->release_image(base, w->shadow_image); + w->shadow_image = NULL; + } + } +} + +static inline bool +_win_bind_image(session_t *ps, struct managed_win *w, void **win_image, void **shadow_image) { + auto pixmap = x_new_id(ps->c); + auto e = xcb_request_check( + ps->c, xcb_composite_name_window_pixmap_checked(ps->c, w->base.id, pixmap)); + if (e) { + log_error("Failed to get named pixmap for window %#010x(%s)", w->base.id, + w->name); + free(e); + return false; + } + log_trace("New named pixmap for %#010x (%s) : %#010x", w->base.id, w->name, pixmap); + *win_image = ps->backend_data->ops->bind_pixmap( + ps->backend_data, pixmap, x_get_visual_info(ps->c, w->a.visual), true); + if (!*win_image) { + return false; + } + if (w->shadow) { + *shadow_image = ps->backend_data->ops->render_shadow( + ps->backend_data, w->widthb, w->heightb, ps->gaussian_map, ps->o.shadow_red, + ps->o.shadow_green, ps->o.shadow_blue, ps->o.shadow_opacity); + if (!*shadow_image) { + log_error("Failed to bind shadow image"); + ps->backend_data->ops->release_image(ps->backend_data, *win_image); + *win_image = NULL; + return false; + } + } + return true; +} + +bool win_bind_image(session_t *ps, struct managed_win *w) { + assert(!w->win_image && !w->shadow_image); + return _win_bind_image(ps, w, &w->win_image, &w->shadow_image); +} + +bool win_try_rebind_image(session_t *ps, struct managed_win *w) { + log_trace("Freeing old window image"); + // Must release first, otherwise breaks NVIDIA driver + win_release_image(ps->backend_data, w); + + return win_bind_image(ps, w); +} + /** * Check if a window has rounded corners. * XXX This is really dumb */ -static bool attr_pure win_has_rounded_corners(const win *w) { +static bool attr_pure win_has_rounded_corners(const struct managed_win *w) { if (!w->bounding_shaped) { return false; } @@ -190,10 +273,10 @@ static bool attr_pure win_has_rounded_corners(const win *w) { // Determine the minimum width/height of a rectangle that could mark // a window as having rounded corners - unsigned short minwidth = - max_i(w->widthb * (1 - ROUNDED_PERCENT), w->widthb - ROUNDED_PIXELS); - unsigned short minheight = - max_i(w->heightb * (1 - ROUNDED_PERCENT), w->heightb - ROUNDED_PIXELS); + auto minwidth = + (uint16_t)max2(w->widthb * (1 - ROUNDED_PERCENT), w->widthb - ROUNDED_PIXELS); + auto minheight = + (uint16_t)max2(w->heightb * (1 - ROUNDED_PERCENT), w->heightb - ROUNDED_PIXELS); // Get the rectangles in the bounding region int nrects = 0; @@ -211,7 +294,7 @@ static bool attr_pure win_has_rounded_corners(const win *w) { return false; } -int win_get_name(session_t *ps, win *w) { +int win_get_name(session_t *ps, struct managed_win *w) { XTextProperty text_prop = {NULL, XCB_NONE, 0, 0}; char **strlst = NULL; int nstr = 0; @@ -219,7 +302,7 @@ int win_get_name(session_t *ps, win *w) { if (!w->client_win) return 0; - if (!(wid_get_text_prop(ps, w->client_win, ps->atom_name_ewmh, &strlst, &nstr))) { + if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) { log_trace("(%#010x): _NET_WM_NAME unset, falling back to WM_NAME.", w->client_win); @@ -230,10 +313,10 @@ int win_get_name(session_t *ps, win *w) { !nstr || !strlst) { if (strlst) XFreeStringList(strlst); - cxfree(text_prop.value); + XFree(text_prop.value); return -1; } - cxfree(text_prop.value); + XFree(text_prop.value); } int ret = 0; @@ -247,15 +330,15 @@ int win_get_name(session_t *ps, win *w) { log_trace("(%#010x): client = %#010x, name = \"%s\", " "ret = %d", - w->id, w->client_win, w->name, ret); + w->base.id, w->client_win, w->name, ret); return ret; } -int win_get_role(session_t *ps, win *w) { +int win_get_role(session_t *ps, struct managed_win *w) { char **strlst = NULL; int nstr = 0; - if (!wid_get_text_prop(ps, w->client_win, ps->atom_role, &strlst, &nstr)) + if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) return -1; int ret = 0; @@ -269,7 +352,7 @@ int win_get_role(session_t *ps, win *w) { log_trace("(%#010x): client = %#010x, role = \"%s\", " "ret = %d", - w->id, w->client_win, w->role, ret); + w->base.id, w->client_win, w->role, ret); return ret; } @@ -293,7 +376,8 @@ static inline bool win_bounding_shaped(const session_t *ps, xcb_window_t wid) { } static wintype_t wid_get_prop_wintype(session_t *ps, xcb_window_t wid) { - winprop_t prop = wid_get_prop(ps, wid, ps->atom_win_type, 32L, XCB_ATOM_ATOM, 32); + winprop_t prop = + wid_get_prop(ps, wid, ps->atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32); for (unsigned i = 0; i < prop.nitems; ++i) { for (wintype_t j = 1; j < NUM_WINTYPES; ++j) { @@ -314,7 +398,8 @@ wid_get_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t def, opacity_t * bool ret = false; *out = def; - winprop_t prop = wid_get_prop(ps, wid, ps->atom_opacity, 1L, XCB_ATOM_CARDINAL, 32); + winprop_t prop = wid_get_prop(ps, wid, ps->atoms->a_NET_WM_WINDOW_OPACITY, 1L, + XCB_ATOM_CARDINAL, 32); if (prop.nitems) { *out = *prop.c32; @@ -327,18 +412,41 @@ wid_get_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t def, opacity_t * } // XXX should distinguish between frame has alpha and window body has alpha -bool win_has_alpha(const win *w) { +bool win_has_alpha(const struct managed_win *w) { return w->pictfmt && w->pictfmt->type == XCB_RENDER_PICT_TYPE_DIRECT && w->pictfmt->direct.alpha_mask; } -winmode_t win_calc_mode(const win *w) { - if (win_has_alpha(w) || w->opacity < 1.0) { +bool win_client_has_alpha(const struct managed_win *w) { + return w->client_pictfmt && w->client_pictfmt->type == XCB_RENDER_PICT_TYPE_DIRECT && + w->client_pictfmt->direct.alpha_mask; +} + +winmode_t win_calc_mode(const struct managed_win *w) { + if (w->opacity < 1.0) { return WMODE_TRANS; } - if (w->frame_opacity != 1.0) { + if (w->frame_opacity != 1.0 && win_has_frame(w)) { return WMODE_FRAME_TRANS; } + + if (win_has_alpha(w)) { + // The WM window has alpha + if (win_client_has_alpha(w)) { + // The client window also has alpha, the entire window is + // transparent + return WMODE_TRANS; + } + if (win_has_frame(w)) { + // The client window doesn't have alpha, but we have a WM frame + // window, which has alpha. + return WMODE_FRAME_TRANS; + } + // Although the WM window has alpha, the frame window has 0 size, so + // consider the window solid + } + + //log_trace("Window %#010x(%s) is solid", w->client_win, w->name); return WMODE_SOLID; } @@ -362,7 +470,7 @@ winmode_t win_calc_mode(const win *w) { * * @return target opacity */ -double win_calc_opacity_target(session_t *ps, const win *w) { +double win_calc_opacity_target(session_t *ps, const struct managed_win *w) { double opacity = 1; if (w->state == WSTATE_UNMAPPED) { @@ -396,13 +504,13 @@ double win_calc_opacity_target(session_t *ps, const win *w) { /** * Determine whether a window is to be dimmed. */ -bool win_should_dim(session_t *ps, const win *w) { +bool win_should_dim(session_t *ps, const struct managed_win *w) { // Make sure we do nothing if the window is unmapped / being destroyed if (w->state == WSTATE_UNMAPPED) { return false; } - if (ps->o.inactive_dim && !(w->focused)) { + if (ps->o.inactive_dim > 0 && !(w->focused)) { return true; } else { return false; @@ -412,7 +520,7 @@ bool win_should_dim(session_t *ps, const win *w) { /** * Determine if a window should fade on opacity change. */ -bool win_should_fade(session_t *ps, const win *w) { +bool win_should_fade(session_t *ps, const struct managed_win *w) { // To prevent it from being overwritten by last-paint value if the window is if (w->fade_force != UNSET) { return w->fade_force; @@ -421,7 +529,7 @@ bool win_should_fade(session_t *ps, const win *w) { return false; } if (ps->o.no_fading_destroyed_argb && w->state == WSTATE_DESTROYING && - win_has_alpha(w) && w->client_win && w->client_win != w->id) { + win_has_alpha(w) && w->client_win && w->client_win != w->base.id) { // deprecated return false; } @@ -440,9 +548,9 @@ bool win_should_fade(session_t *ps, const win *w) { * * The property must be set on the outermost window, usually the WM frame. */ -void win_update_prop_shadow_raw(session_t *ps, win *w) { - winprop_t prop = - wid_get_prop(ps, w->id, ps->atom_compton_shadow, 1, XCB_ATOM_CARDINAL, 32); +void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w) { + winprop_t prop = wid_get_prop(ps, w->base.id, ps->atoms->a_COMPTON_SHADOW, 1, + XCB_ATOM_CARDINAL, 32); if (!prop.nitems) { w->prop_shadow = -1; @@ -457,7 +565,7 @@ void win_update_prop_shadow_raw(session_t *ps, win *w) { * Reread _COMPTON_SHADOW property from a window and update related * things. */ -void win_update_prop_shadow(session_t *ps, win *w) { +void win_update_prop_shadow(session_t *ps, struct managed_win *w) { long attr_shadow_old = w->prop_shadow; win_update_prop_shadow_raw(ps, w); @@ -466,9 +574,25 @@ void win_update_prop_shadow(session_t *ps, win *w) { win_determine_shadow(ps, w); } -void win_set_shadow(session_t *ps, win *w, bool shadow_new) { - if (w->shadow == shadow_new) +void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new) { + if (w->shadow == shadow_new) { return; + } + + log_debug("Updating shadow property of window %#010x (%s) to %d", w->base.id, + w->name, shadow_new); + if (ps->backend_data && w->state != WSTATE_UNMAPPED && + !(w->flags & WIN_FLAGS_IMAGE_ERROR)) { + assert(!w->shadow_image); + // Create shadow image + w->shadow_image = ps->backend_data->ops->render_shadow( + ps->backend_data, w->widthb, w->heightb, ps->gaussian_map, ps->o.shadow_red, + ps->o.shadow_green, ps->o.shadow_blue, ps->o.shadow_opacity); + if (!w->shadow_image) { + log_error("Failed to bind shadow image"); + w->shadow_force = OFF; + } + } region_t extents; pixman_region32_init(&extents); @@ -480,8 +604,9 @@ void win_set_shadow(session_t *ps, win *w, bool shadow_new) { // Shadow geometry currently doesn't change on shadow state change // calc_shadow_geometry(ps, w); // Mark the old extents as damaged if the shadow is removed - if (!w->shadow) + if (!w->shadow) { add_damage(ps, &extents); + } pixman_region32_clear(&extents); // Mark the new extents as damaged if the shadow is added @@ -496,22 +621,34 @@ void win_set_shadow(session_t *ps, win *w, bool shadow_new) { * Determine if a window should have shadow, and update things depending * on shadow state. */ -void win_determine_shadow(session_t *ps, win *w) { +void win_determine_shadow(session_t *ps, struct managed_win *w) { + log_debug("Determining shadow of window %#010x (%s)", w->base.id, w->name); bool shadow_new = w->shadow; - if (UNSET != w->shadow_force) + if (w->shadow_force != UNSET) { shadow_new = w->shadow_force; - else if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) - shadow_new = (ps->o.wintype_option[w->window_type].shadow && - !c2_match(ps, w, ps->o.shadow_blacklist, NULL) && - !(ps->o.shadow_ignore_shaped && w->bounding_shaped && - !w->rounded_corners) && - !(ps->o.respect_prop_shadow && 0 == w->prop_shadow)); + } else if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { + shadow_new = true; + if (!ps->o.wintype_option[w->window_type].shadow) { + log_debug("Shadow disabled by wintypes"); + shadow_new = false; + } else if (c2_match(ps, w, ps->o.shadow_blacklist, NULL)) { + log_debug("Shadow disabled by shadow-exclude"); + shadow_new = false; + } else if (ps->o.shadow_ignore_shaped && w->bounding_shaped && + !w->rounded_corners) { + log_debug("Shadow disabled by shadow-ignore-shaped"); + shadow_new = false; + } else if (ps->o.respect_prop_shadow && w->prop_shadow == 0) { + log_debug("Shadow disabled by shadow property"); + shadow_new = false; + } + } win_set_shadow(ps, w, shadow_new); } -void win_set_invert_color(session_t *ps, win *w, bool invert_color_new) { +void win_set_invert_color(session_t *ps, struct managed_win *w, bool invert_color_new) { if (w->invert_color == invert_color_new) return; @@ -520,10 +657,52 @@ void win_set_invert_color(session_t *ps, win *w, bool invert_color_new) { add_damage_from_win(ps, w); } +/** + * Set w->invert_color_force of a window. + */ +void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t val) { + if (val != w->invert_color_force) { + w->invert_color_force = val; + win_determine_invert_color(ps, w); + queue_redraw(ps); + } +} + +/** + * Set w->fade_force of a window. + * + * Doesn't affect fading already in progress + */ +void win_set_fade_force(session_t *ps, struct managed_win *w, switch_t val) { + w->fade_force = val; +} + +/** + * Set w->focused_force of a window. + */ +void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val) { + if (val != w->focused_force) { + w->focused_force = val; + win_update_focused(ps, w); + queue_redraw(ps); + } +} + +/** + * Set w->shadow_force of a window. + */ +void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val) { + if (val != w->shadow_force) { + w->shadow_force = val; + win_determine_shadow(ps, w); + queue_redraw(ps); + } +} + /** * Determine if a window should have color inverted. */ -void win_determine_invert_color(session_t *ps, win *w) { +void win_determine_invert_color(session_t *ps, struct managed_win *w) { bool invert_color_new = w->invert_color; if (UNSET != w->invert_color_force) @@ -534,27 +713,26 @@ void win_determine_invert_color(session_t *ps, win *w) { win_set_invert_color(ps, w, invert_color_new); } -void win_set_blur_background(session_t *ps, win *w, bool blur_background_new) { +static void win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_background_new) { if (w->blur_background == blur_background_new) return; w->blur_background = blur_background_new; - // Only consider window damaged if it's previously painted with background - // blurred - if (!win_is_solid(ps, w) || (ps->o.blur_background_frame && w->frame_opacity != 1)) - add_damage_from_win(ps, w); + // This damage might not be absolutely necessary (e.g. when the window is opaque), + // but blur_background changes should be rare, so this should be fine. + add_damage_from_win(ps, w); } /** * Determine if a window should have background blurred. */ -void win_determine_blur_background(session_t *ps, win *w) { +void win_determine_blur_background(session_t *ps, struct managed_win *w) { if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) return; - bool blur_background_new = ps->o.blur_background && - !c2_match(ps, w, ps->o.blur_background_blacklist, NULL); + bool blur_background_new = + ps->o.blur_method && !c2_match(ps, w, ps->o.blur_background_blacklist, NULL); win_set_blur_background(ps, w, blur_background_new); } @@ -565,7 +743,7 @@ void win_determine_blur_background(session_t *ps, win *w) { * TODO This override the window's opacity property, may not be * a good idea. */ -void win_update_opacity_rule(session_t *ps, win *w) { +void win_update_opacity_rule(session_t *ps, struct managed_win *w) { if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) return; @@ -582,16 +760,17 @@ void win_update_opacity_rule(session_t *ps, win *w) { w->opacity_set = opacity; w->opacity_is_set = is_set; - if (!is_set) - wid_rm_opacity_prop(ps, w->id); - else - wid_set_opacity_prop(ps, w->id, opacity * OPAQUE); + if (!is_set) { + wid_rm_opacity_prop(ps, w->base.id); + } else { + wid_set_opacity_prop(ps, w->base.id, (opacity_t)(opacity * OPAQUE)); + } } /** * Function to be called on window type changes. */ -void win_on_wtype_change(session_t *ps, win *w) { +void win_on_wtype_change(session_t *ps, struct managed_win *w) { win_determine_shadow(ps, w); win_update_focused(ps, w); if (ps->o.invert_color_list) @@ -605,7 +784,7 @@ void win_on_wtype_change(session_t *ps, win *w) { * * TODO need better name */ -void win_on_factor_change(session_t *ps, win *w) { +void win_on_factor_change(session_t *ps, struct managed_win *w) { if (ps->o.shadow_blacklist) win_determine_shadow(ps, w); if (ps->o.invert_color_list) @@ -627,46 +806,29 @@ void win_on_factor_change(session_t *ps, win *w) { /** * Update cache data in struct _win that depends on window size. */ -void win_on_win_size_change(session_t *ps, win *w) { +void win_on_win_size_change(session_t *ps, struct managed_win *w) { w->widthb = w->g.width + w->g.border_width * 2; w->heightb = w->g.height + w->g.border_width * 2; w->shadow_dx = ps->o.shadow_offset_x; w->shadow_dy = ps->o.shadow_offset_y; w->shadow_width = w->widthb + ps->o.shadow_radius * 2; w->shadow_height = w->heightb + ps->o.shadow_radius * 2; - w->flags |= WFLAG_SIZE_CHANGE; + // Invalidate the shadow we built - if (ps->o.experimental_backends && ps->redirected) { - if (w->state == WSTATE_MAPPED || w->state == WSTATE_MAPPING || - w->state == WSTATE_FADING) { - ps->backend_data->ops->release_image(ps->backend_data, w->win_image); - if (w->shadow_image) { - ps->backend_data->ops->release_image(ps->backend_data, - w->shadow_image); - } - auto pixmap = xcb_generate_id(ps->c); - xcb_composite_name_window_pixmap(ps->c, w->id, pixmap); - w->win_image = ps->backend_data->ops->bind_pixmap( - ps->backend_data, pixmap, - x_get_visual_info(ps->c, w->a.visual), true); - if (w->shadow) { - w->shadow_image = ps->backend_data->ops->render_shadow( - ps->backend_data, w->widthb, w->heightb, - ps->gaussian_map, ps->o.shadow_red, ps->o.shadow_green, - ps->o.shadow_blue, ps->o.shadow_opacity); - } - } else { - assert(w->state == WSTATE_UNMAPPED); - } + if (w->state == WSTATE_MAPPED || w->state == WSTATE_MAPPING || + w->state == WSTATE_FADING) { + w->flags |= WIN_FLAGS_IMAGE_STALE; + ps->pending_updates = true; } else { - free_paint(ps, &w->shadow_paint); + assert(w->state == WSTATE_UNMAPPED); } + free_paint(ps, &w->shadow_paint); } /** * Update window type. */ -void win_update_wintype(session_t *ps, win *w) { +void win_update_wintype(session_t *ps, struct managed_win *w) { const wintype_t wtype_old = w->window_type; // Detect window type here @@ -677,7 +839,7 @@ void win_update_wintype(session_t *ps, win *w) { // _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_prop(ps, w->client_win, ps->atom_transient)) + !wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) w->window_type = WINTYPE_NORMAL; else w->window_type = WINTYPE_DIALOG; @@ -694,7 +856,7 @@ void win_update_wintype(session_t *ps, win *w) { * @param w struct _win of the parent window * @param client window ID of the client window */ -void win_mark_client(session_t *ps, win *w, xcb_window_t client) { +void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) { w->client_win = client; // If the window isn't mapped yet, stop here, as the function will be @@ -733,6 +895,16 @@ void win_mark_client(session_t *ps, win *w, xcb_window_t client) { // Update window focus state win_update_focused(ps, w); + + auto r = xcb_get_window_attributes_reply( + ps->c, xcb_get_window_attributes(ps->c, w->client_win), NULL); + if (!r) { + log_error("Failed to get client window attributes"); + return; + } + + w->client_pictfmt = x_get_pictform_for_visual(ps->c, r->visual); + free(r); } /** @@ -741,7 +913,7 @@ void win_mark_client(session_t *ps, win *w, xcb_window_t client) { * @param ps current session * @param w struct _win of the parent window */ -void win_unmark_client(session_t *ps, win *w) { +void win_unmark_client(session_t *ps, struct managed_win *w) { xcb_window_t client = w->client_win; w->client_win = XCB_NONE; @@ -758,7 +930,7 @@ void win_unmark_client(session_t *ps, win *w) { * @param ps current session * @param w struct _win of the parent window */ -void win_recheck_client(session_t *ps, win *w) { +void win_recheck_client(session_t *ps, struct managed_win *w) { // Initialize wmwin to false w->wmwin = false; @@ -766,15 +938,16 @@ void win_recheck_client(session_t *ps, win *w) { // Always recursively look for a window with WM_STATE, as Fluxbox // sets override-redirect flags on all frame windows. - xcb_window_t cw = find_client_win(ps, w->id); - if (cw) - log_trace("(%#010x): client %#010x", w->id, cw); + xcb_window_t cw = find_client_win(ps, w->base.id); + if (cw) { + log_trace("(%#010x): client %#010x", w->base.id, cw); + } // Set a window's client window to itself if we couldn't find a // client window if (!cw) { - cw = w->id; + cw = w->base.id; w->wmwin = !w->a.override_redirect; - log_trace("(%#010x): client self (%s)", w->id, + log_trace("(%#010x): client self (%s)", w->base.id, (w->wmwin ? "wmwin" : "override-redirected")); } @@ -789,7 +962,7 @@ void win_recheck_client(session_t *ps, win *w) { /** * Free all resources in a struct _win. */ -void free_win_res(session_t *ps, win *w) { +void free_win_res(session_t *ps, struct managed_win *w) { // No need to call backend release_image here because // finish_unmap_win should've done that for us. // XXX unless we are called by session_destroy @@ -810,9 +983,56 @@ void free_win_res(session_t *ps, win *w) { free(w->role); } -// TODO: probably split into win_new (in win.c) and add_win (in compton.c) -void add_win(session_t *ps, xcb_window_t id, xcb_window_t prev) { - static const win win_def = { +/// Insert a new window after list_node `prev` +/// New window will be in unmapped state +static struct win *add_win(session_t *ps, xcb_window_t id, struct list_node *prev) { + log_debug("Adding window %#010x", id); + struct win *old_w = NULL; + HASH_FIND_INT(ps->windows, &id, old_w); + assert(old_w == NULL); + + auto new_w = cmalloc(struct win); + list_insert_after(prev, &new_w->stack_neighbour); + new_w->id = id; + new_w->managed = false; + new_w->is_new = true; + new_w->destroyed = false; + + HASH_ADD_INT(ps->windows, id, new_w); + ps->pending_updates = true; + return new_w; +} + +/// Insert a new win entry at the top of the stack +struct win *add_win_top(session_t *ps, xcb_window_t id) { + return add_win(ps, id, &ps->window_stack); +} + +/// Insert a new window above window with id `below`, if there is no window, add to top +/// New window will be in unmapped state +struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) { + struct win *w = NULL; + HASH_FIND_INT(ps->windows, &below, w); + if (!w) { + if (!list_is_empty(&ps->window_stack)) { + // `below` window is not found even if the window stack is not + // empty + return NULL; + } + return add_win_top(ps, id); + } else { + // we found something from the hash table, so if the stack is empty, + // we are in an inconsistent state. + assert(!list_is_empty(&ps->window_stack)); + return add_win(ps, id, w->stack_neighbour.prev); + } +} + +/// Query the Xorg for information about window `win` +/// `win` pointer might become invalid after this function returns +/// Returns the pointer to the window, might be different from `w` +struct win *fill_win(session_t *ps, struct win *w) { + static const struct managed_win win_def = { // No need to initialize. (or, you can think that // they are initialized right here). // The following ones are updated during paint or paint preprocess @@ -828,8 +1048,6 @@ void add_win(session_t *ps, xcb_window_t id, xcb_window_t prev) { .state = WSTATE_UNMAPPED, // updated by window state changes .in_openclose = true, // set to false after first map is done, // true here because window is just created - .need_configure = false, // set to true when window is configured - // while unmapped. .queue_configure = {}, // same as above .reg_ignore_valid = false, // set to true when damaged .flags = 0, // updated by property/attributes/etc change @@ -841,10 +1059,9 @@ void add_win(session_t *ps, xcb_window_t id, xcb_window_t prev) { .invert_color_force = UNSET, // Initialized in this function - .next = NULL, - .id = XCB_NONE, .a = {0}, .pictfmt = NULL, + .client_pictfmt = NULL, .widthb = 0, .heightb = 0, .shadow_dx = 0, @@ -893,93 +1110,90 @@ void add_win(session_t *ps, xcb_window_t id, xcb_window_t prev) { .shadow_paint = PAINT_INIT, }; + assert(!w->destroyed); + assert(w->is_new); + + w->is_new = false; + // Reject overlay window and already added windows - if (id == ps->overlay) { - return; + if (w->id == ps->overlay) { + return w; } - auto duplicated_win = find_win(ps, id); + auto duplicated_win = find_managed_win(ps, w->id); if (duplicated_win) { - log_debug("Window %#010x (recorded name: %s) added multiple times", id, + log_debug("Window %#010x (recorded name: %s) added multiple times", w->id, duplicated_win->name); - return; + return &duplicated_win->base; } - log_debug("Adding window %#010x, prev %#010x", id, prev); - xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c, id); - xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(ps->c, id); + log_debug("Managing window %#010x", w->id); + xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c, w->id); xcb_get_window_attributes_reply_t *a = xcb_get_window_attributes_reply(ps->c, acookie, NULL); - xcb_get_geometry_reply_t *g = xcb_get_geometry_reply(ps->c, gcookie, NULL); - if (!a || !g || a->map_state == XCB_MAP_STATE_UNVIEWABLE) { + if (!a || a->map_state == XCB_MAP_STATE_UNVIEWABLE) { // Failed to get window attributes or geometry probably means // the window is gone already. Unviewable means the window is // already reparented elsewhere. // BTW, we don't care about Input Only windows, except for stacking // proposes, so we need to keep track of them still. free(a); - free(g); - return; + return w; + } + + if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) { + // No need to manage this window, but we still keep it on the window stack + w->managed = false; + return w; } // Allocate and initialize the new win structure - auto new = cmalloc(win); + auto new = cmalloc(struct managed_win); // Fill structure // We only need to initialize the part that are not initialized // by map_win *new = win_def; - new->id = id; + new->base = *w; + new->base.managed = true; new->a = *a; - new->g = *g; pixman_region32_init(&new->bounding_shape); - free(g); free(a); // Create Damage for window (if not Input Only) - if (new->a._class != XCB_WINDOW_CLASS_INPUT_ONLY) { - new->damage = xcb_generate_id(ps->c); - xcb_generic_error_t *e = xcb_request_check( - ps->c, xcb_damage_create_checked(ps->c, new->damage, id, - XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY)); - if (e) { - free(e); - free(new); - return; - } - - new->pictfmt = x_get_pictform_for_visual(ps->c, new->a.visual); + new->damage = x_new_id(ps->c); + xcb_generic_error_t *e = xcb_request_check( + ps->c, xcb_damage_create_checked(ps->c, new->damage, w->id, + XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY)); + if (e) { + free(e); + free(new); + return w; } - win_on_win_size_change(ps, new); + new->pictfmt = x_get_pictform_for_visual(ps->c, new->a.visual); + new->client_pictfmt = NULL; - // Find window insertion point - win **p = NULL; - if (prev) { - for (p = &ps->list; *p; p = &(*p)->next) { - if ((*p)->id == prev && (*p)->state != WSTATE_DESTROYING) - break; - } - } else { - p = &ps->list; - } - new->next = *p; - *p = new; + list_replace(&w->stack_neighbour, &new->base.stack_neighbour); + struct win *replaced = NULL; + HASH_REPLACE_INT(ps->windows, id, &new->base, replaced); + assert(replaced == w); + free(w); #ifdef CONFIG_DBUS // Send D-Bus signal if (ps->o.dbus) { - cdbus_ev_win_added(ps, new); + cdbus_ev_win_added(ps, &new->base); } #endif - return; + return &new->base; } /** * Update focused state of a window. */ -void win_update_focused(session_t *ps, win *w) { +void win_update_focused(session_t *ps, struct managed_win *w) { if (UNSET != w->focused_force) { w->focused = w->focused_force; } else { @@ -989,7 +1203,7 @@ void win_update_focused(session_t *ps, win *w) { // windows specially if (ps->o.wintype_option[w->window_type].focus || (ps->o.mark_wmwin_focused && w->wmwin) || - (ps->o.mark_ovredir_focused && w->id == w->client_win && !w->wmwin) || + (ps->o.mark_ovredir_focused && w->base.id == w->client_win && !w->wmwin) || (w->a.map_state == XCB_MAP_STATE_VIEWABLE && c2_match(ps, w, ps->o.focus_blacklist, NULL))) w->focused = true; @@ -1018,7 +1232,7 @@ void win_update_focused(session_t *ps, win *w) { /** * Set leader of a window. */ -static inline void win_set_leader(session_t *ps, win *w, xcb_window_t nleader) { +static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_window_t nleader) { // If the leader changes if (w->leader != nleader) { xcb_window_t cache_leader_old = win_get_leader(ps, w); @@ -1051,26 +1265,26 @@ static inline void win_set_leader(session_t *ps, win *w, xcb_window_t nleader) { /** * Update leader of a window. */ -void win_update_leader(session_t *ps, win *w) { +void win_update_leader(session_t *ps, struct managed_win *w) { xcb_window_t leader = XCB_NONE; // Read the leader properties if (ps->o.detect_transient && !leader) - leader = wid_get_prop_window(ps, w->client_win, ps->atom_transient); + leader = wid_get_prop_window(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR); if (ps->o.detect_client_leader && !leader) - leader = wid_get_prop_window(ps, w->client_win, ps->atom_client_leader); + leader = wid_get_prop_window(ps, w->client_win, ps->atoms->aWM_CLIENT_LEADER); win_set_leader(ps, w, leader); - log_trace("(%#010x): client %#010x, leader %#010x, cache %#010x", w->id, + log_trace("(%#010x): client %#010x, leader %#010x, cache %#010x", w->base.id, w->client_win, w->leader, win_get_leader(ps, w)); } /** * Internal function of win_get_leader(). */ -xcb_window_t win_get_leader_raw(session_t *ps, win *w, int recursions) { +xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions) { // Rebuild the cache if needed if (!w->cache_leader && (w->client_win || w->leader)) { // Leader defaults to client window @@ -1079,7 +1293,7 @@ xcb_window_t win_get_leader_raw(session_t *ps, win *w, int recursions) { // If the leader of this window isn't itself, look for its ancestors if (w->cache_leader && w->cache_leader != w->client_win) { - win *wp = find_toplevel(ps, w->cache_leader); + auto wp = find_toplevel(ps, w->cache_leader); if (wp) { // Dead loop? if (recursions > WIN_GET_LEADER_MAX_RECURSION) @@ -1097,7 +1311,7 @@ xcb_window_t win_get_leader_raw(session_t *ps, win *w, int recursions) { * Retrieve the WM_CLASS of a window and update its * win structure. */ -bool win_get_class(session_t *ps, win *w) { +bool win_get_class(session_t *ps, struct managed_win *w) { char **strlst = NULL; int nstr = 0; @@ -1112,7 +1326,7 @@ bool win_get_class(session_t *ps, win *w) { w->class_general = NULL; // Retrieve the property string list - if (!wid_get_text_prop(ps, w->client_win, ps->atom_class, &strlst, &nstr)) + if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_CLASS, &strlst, &nstr)) return false; // Copy the strings if successful @@ -1125,7 +1339,7 @@ bool win_get_class(session_t *ps, win *w) { log_trace("(%#010x): client = %#010x, " "instance = \"%s\", general = \"%s\"", - w->id, w->client_win, w->class_instance, w->class_general); + w->base.id, w->client_win, w->class_instance, w->class_general); return true; } @@ -1133,7 +1347,7 @@ bool win_get_class(session_t *ps, win *w) { /** * Handle window focus change. */ -static void win_on_focus_change(session_t *ps, win *w) { +static void win_on_focus_change(session_t *ps, struct managed_win *w) { // If window grouping detection is enabled if (ps->o.track_leader) { xcb_window_t leader = win_get_leader(ps, w); @@ -1169,9 +1383,9 @@ static void win_on_focus_change(session_t *ps, win *w) { // Send D-Bus signal if (ps->o.dbus) { if (win_is_focused_real(ps, w)) - cdbus_ev_win_focusin(ps, w); + cdbus_ev_win_focusin(ps, &w->base); else - cdbus_ev_win_focusout(ps, w); + cdbus_ev_win_focusout(ps, &w->base); } #endif } @@ -1179,23 +1393,23 @@ static void win_on_focus_change(session_t *ps, win *w) { /** * Set real focused state of a window. */ -void win_set_focused(session_t *ps, win *w, bool focused) { +void win_set_focused(session_t *ps, struct managed_win *w) { // Unmapped windows will have their focused state reset on map - if (w->a.map_state == XCB_MAP_STATE_UNMAPPED) + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { return; + } - if (win_is_focused_real(ps, w) == focused) + if (win_is_focused_real(ps, w)) { return; + } - if (focused) { - if (ps->active_win) - win_set_focused(ps, ps->active_win, false); - ps->active_win = w; - } else if (w == ps->active_win) - ps->active_win = NULL; - - assert(win_is_focused_real(ps, w) == focused); + auto old_active_win = ps->active_win; + ps->active_win = w; + assert(win_is_focused_real(ps, w)); + if (old_active_win) { + win_on_focus_change(ps, old_active_win); + } win_on_focus_change(ps, w); } @@ -1205,26 +1419,28 @@ void win_set_focused(session_t *ps, win *w, bool focused) { * Note w->shadow and shadow geometry must be correct before calling this * function. */ -void win_extents(const win *w, region_t *res) { +void win_extents(const struct managed_win *w, region_t *res) { pixman_region32_clear(res); - pixman_region32_union_rect(res, res, w->g.x, w->g.y, w->widthb, w->heightb); + pixman_region32_union_rect(res, res, w->g.x, w->g.y, (uint)w->widthb, (uint)w->heightb); - if (w->shadow) + if (w->shadow) { + assert(w->shadow_width >= 0 && w->shadow_height >= 0); pixman_region32_union_rect(res, res, w->g.x + w->shadow_dx, - w->g.y + w->shadow_dy, w->shadow_width, - w->shadow_height); + w->g.y + w->shadow_dy, (uint)w->shadow_width, + (uint)w->shadow_height); + } } gen_by_val(win_extents); - /** - * Update the out-dated bounding shape of a window. - * - * Mark the window shape as updated - */ - void win_update_bounding_shape(session_t *ps, win *w) { +/** + * Update the out-dated bounding shape of a window. + * + * Mark the window shape as updated + */ +void win_update_bounding_shape(session_t *ps, struct managed_win *w) { if (ps->shape_exists) - w->bounding_shaped = win_bounding_shaped(ps, w->id); + w->bounding_shaped = win_bounding_shaped(ps, w->base.id); pixman_region32_clear(&w->bounding_shape); // Start with the window rectangular region @@ -1239,7 +1455,8 @@ gen_by_val(win_extents); */ xcb_shape_get_rectangles_reply_t *r = xcb_shape_get_rectangles_reply( - ps->c, xcb_shape_get_rectangles(ps->c, w->id, XCB_SHAPE_SK_BOUNDING), NULL); + ps->c, + xcb_shape_get_rectangles(ps->c, w->base.id, XCB_SHAPE_SK_BOUNDING), NULL); if (!r) break; @@ -1274,34 +1491,16 @@ gen_by_val(win_extents); } // Window shape changed, we should free old wpaint and shadow pict - if (ps->o.experimental_backends) { - // log_trace("free out dated pict"); - // Window shape changed, we should free win_data - if (ps->redirected && w->state != WSTATE_UNMAPPED) { - // Note we only do this when screen is redirected, because - // otherwise win_data is not valid - assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING); - ps->backend_data->ops->release_image(ps->backend_data, w->win_image); - if (w->shadow_image) { - ps->backend_data->ops->release_image(ps->backend_data, - w->shadow_image); - } - auto pixmap = xcb_generate_id(ps->c); - xcb_composite_name_window_pixmap(ps->c, w->id, pixmap); - w->win_image = ps->backend_data->ops->bind_pixmap( - ps->backend_data, pixmap, - x_get_visual_info(ps->c, w->a.visual), true); - if (w->shadow) { - w->shadow_image = ps->backend_data->ops->render_shadow( - ps->backend_data, w->widthb, w->heightb, - ps->gaussian_map, ps->o.shadow_red, ps->o.shadow_green, - ps->o.shadow_blue, ps->o.shadow_opacity); - } - } - } else { - free_paint(ps, &w->paint); - free_paint(ps, &w->shadow_paint); + // log_trace("free out dated pict"); + if (w->state != WSTATE_UNMAPPED) { + // Note we only do this when screen is redirected, because + // otherwise win_data is not valid + assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING); + w->flags |= WIN_FLAGS_IMAGE_STALE; + ps->pending_updates = true; } + free_paint(ps, &w->paint); + free_paint(ps, &w->shadow_paint); win_on_factor_change(ps, w); } @@ -1309,15 +1508,15 @@ gen_by_val(win_extents); /** * Reread opacity property of a window. */ -void win_update_opacity_prop(session_t *ps, win *w) { +void win_update_opacity_prop(session_t *ps, struct managed_win *w) { // get frame opacity first - w->has_opacity_prop = wid_get_opacity_prop(ps, w->id, OPAQUE, &w->opacity_prop); + w->has_opacity_prop = wid_get_opacity_prop(ps, w->base.id, OPAQUE, &w->opacity_prop); if (w->has_opacity_prop) // opacity found return; - if (ps->o.detect_client_opacity && w->client_win && w->id == w->client_win) + if (ps->o.detect_client_opacity && w->client_win && w->base.id == w->client_win) // checking client opacity not allowed return; @@ -1329,12 +1528,17 @@ void win_update_opacity_prop(session_t *ps, win *w) { /** * Retrieve frame extents from a window. */ -void win_update_frame_extents(session_t *ps, win *w, xcb_window_t client) { - winprop_t prop = - wid_get_prop(ps, client, ps->atom_frame_extents, 4L, XCB_ATOM_CARDINAL, 32); +void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client) { + winprop_t prop = wid_get_prop(ps, client, ps->atoms->a_NET_FRAME_EXTENTS, 4L, + XCB_ATOM_CARDINAL, 32); if (prop.nitems == 4) { - const uint32_t *const extents = prop.c32; + const int32_t extents[4] = { + to_int_checked(prop.c32[0]), + to_int_checked(prop.c32[1]), + to_int_checked(prop.c32[2]), + to_int_checked(prop.c32[3]), + }; const bool changed = w->frame_extents.left != extents[0] || w->frame_extents.right != extents[1] || w->frame_extents.top != extents[2] || @@ -1350,14 +1554,14 @@ void win_update_frame_extents(session_t *ps, win *w, xcb_window_t client) { w->reg_ignore_valid = false; } - log_trace("(%#010x): %d, %d, %d, %d", w->id, w->frame_extents.left, + log_trace("(%#010x): %d, %d, %d, %d", w->base.id, w->frame_extents.left, w->frame_extents.right, w->frame_extents.top, w->frame_extents.bottom); free_winprop(&prop); } -bool win_is_region_ignore_valid(session_t *ps, const win *w) { - for (win *i = ps->list; w; w = w->next) { +bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) { + win_stack_foreach_managed(i, &ps->window_stack) { if (i == w) break; if (!i->reg_ignore_valid) @@ -1369,11 +1573,16 @@ bool win_is_region_ignore_valid(session_t *ps, const win *w) { /** * Stop listening for events on a particular window. */ -void win_ev_stop(session_t *ps, const win *w) { +void win_ev_stop(session_t *ps, const struct win *w) { xcb_change_window_attributes(ps->c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); - if (w->client_win) { - xcb_change_window_attributes(ps->c, w->client_win, XCB_CW_EVENT_MASK, + if (!w->managed) { + return; + } + + auto mw = (struct managed_win *)w; + if (mw->client_win) { + xcb_change_window_attributes(ps->c, mw->client_win, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); } @@ -1382,34 +1591,24 @@ void win_ev_stop(session_t *ps, const win *w) { } } -static void finish_unmap_win(session_t *ps, win **_w) { - win *w = *_w; +static void finish_unmap_win(session_t *ps, struct managed_win **_w) { + auto w = *_w; w->ever_damaged = false; w->reg_ignore_valid = false; w->state = WSTATE_UNMAPPED; - w->flags = 0; - if (ps->o.experimental_backends) { - // We are in unmap_win, we definitely was viewable - if (ps->redirected) { - assert(w->win_image); - ps->backend_data->ops->release_image(ps->backend_data, w->win_image); - if (w->shadow_image) { - ps->backend_data->ops->release_image(ps->backend_data, - w->shadow_image); - } - w->win_image = NULL; - w->shadow_image = NULL; - } - } else { - free_paint(ps, &w->paint); - free_paint(ps, &w->shadow_paint); + // We are in unmap_win, this window definitely was viewable + if (ps->backend_data) { + win_release_image(ps->backend_data, w); } + free_paint(ps, &w->paint); + free_paint(ps, &w->shadow_paint); + + w->flags = 0; } -static void finish_destroy_win(session_t *ps, win **_w) { - win *w = *_w; - win **prev = NULL; +static void finish_destroy_win(session_t *ps, struct managed_win **_w) { + auto w = *_w; if (w->state != WSTATE_UNMAPPED) { // Only UNMAPPED state has window resources freed, otherwise @@ -1424,51 +1623,143 @@ static void finish_destroy_win(session_t *ps, win **_w) { // paint happened at least once, w->reg_ignore_valid would // be true, and there is no need to invalid w->next->reg_ignore // when w is destroyed. - if (w->next) { - // should be `= w->reg_ignore_valid && w->next->reg_ignore_valid`, - // but keep it this way until we think about reg_ignore. - w->next->reg_ignore_valid = false; + auto next_w = win_stack_find_next_managed(ps, &w->base.stack_neighbour); + if (next_w) { + rc_region_unref(&next_w->reg_ignore); + next_w->reg_ignore_valid = false; + } + + log_trace("Trying to destroy (%#010x)", w->base.id); + list_remove(&w->base.stack_neighbour); + + if (w == ps->active_win) { + // Usually, the window cannot be the focused at destruction. FocusOut + // should be generated before the window is destroyed. + // We do this check just to be completely sure we don't have dangling + // references. + log_debug("window %#010x (%s) is destroyed while being focused", + w->base.id, w->name); + ps->active_win = NULL; } - log_trace("Trying to destroy (%#010x)", w->id); - for (prev = &ps->list; *prev; prev = &(*prev)->next) { - if (w == *prev) { - log_trace("Found (%#010x \"%s\")", w->id, w->name); - *prev = w->next; - - if (w == ps->active_win) { - ps->active_win = NULL; - } - - if (!ps->o.experimental_backends) { - free_win_res(ps, w); - } + free_win_res(ps, w); - // Drop w from all prev_trans to avoid accessing freed memory in - // repair_win() - // TODO there can only be one prev_trans pointing to w - for (win *w2 = ps->list; w2; w2 = w2->next) { - if (w == w2->prev_trans) { - w2->prev_trans = NULL; - } - } - free(w); - *_w = NULL; - return; + // Drop w from all prev_trans to avoid accessing freed memory in + // repair_win() + // TODO there can only be one prev_trans pointing to w + win_stack_foreach_managed(w2, &ps->window_stack) { + if (w == w2->prev_trans) { + w2->prev_trans = NULL; } } + free(w); + *_w = NULL; + return; log_warn("Destroyed window is not in window list"); assert(false); } -static void finish_map_win(session_t *ps, win **_w) { - win *w = *_w; +static void finish_map_win(session_t *ps, struct managed_win **_w) { + auto w = *_w; w->in_openclose = false; w->state = WSTATE_MAPPED; } -void unmap_win(session_t *ps, win **_w, bool destroy) { - win *w = *_w; +void destroy_unmanaged_win(session_t *ps, struct win **_w) { + auto w = *_w; + assert(!w->managed); + assert(!w->destroyed); + list_remove(&w->stack_neighbour); + HASH_DEL(ps->windows, w); + free(w); + *_w = NULL; +} + +/// Move window `w` so it's before `next` in the list +static inline void restack_win(session_t *ps, struct win *w, struct list_node *next) { + struct managed_win *mw = NULL; + if (w->managed) { + mw = (struct managed_win *)w; + } + + if (mw) { + // This invalidates all reg_ignore below the new stack position of `w` + mw->reg_ignore_valid = false; + rc_region_unref(&mw->reg_ignore); + + // This invalidates all reg_ignore below the old stack position of `w` + auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour); + if (next_w) { + next_w->reg_ignore_valid = false; + rc_region_unref(&next_w->reg_ignore); + } + } + + list_move_before(&w->stack_neighbour, next); + + // add damage for this window + if (mw) { + add_damage_from_win(ps, mw); + } + +#ifdef DEBUG_RESTACK + log_trace("Window stack modified. Current stack:"); + for (auto c = ps->list; c; c = c->next) { + const char *desc = ""; + if (c->state == WSTATE_DESTROYING) { + desc = "(D) "; + } + log_trace("%#010x \"%s\" %s", c->id, c->name, desc); + } +#endif +} + +/// Move window `w` so it's right above `below` +void restack_above(session_t *ps, struct win *w, xcb_window_t below) { + xcb_window_t old_below; + + if (!list_node_is_last(&ps->window_stack, &w->stack_neighbour)) { + old_below = list_next_entry(w, stack_neighbour)->id; + } else { + old_below = XCB_NONE; + } + log_debug("Restack %#010x (%s), old_below: %#010x, new_below: %#010x", w->id, + win_get_name_if_managed(w), old_below, below); + + if (old_below != below) { + struct list_node *new_next; + if (!below) { + new_next = &ps->window_stack; + } else { + struct win *tmp_w = NULL; + HASH_FIND_INT(ps->windows, &below, tmp_w); + + if (!tmp_w) { + log_error("Failed to found new below window %#010x.", below); + return; + } + + new_next = &tmp_w->stack_neighbour; + } + restack_win(ps, w, new_next); + } +} + +void restack_bottom(session_t *ps, struct win *w) { + restack_above(ps, w, 0); +} + +void restack_top(session_t *ps, struct win *w) { + log_debug("Restack %#010x (%s) to top", w->id, win_get_name_if_managed(w)); + if (&w->stack_neighbour == ps->window_stack.next) { + // already at top + return; + } + restack_win(ps, w, ps->window_stack.next); +} + +void unmap_win(session_t *ps, struct managed_win **_w, bool destroy) { + auto w = *_w; winstate_t target_state = destroy ? WSTATE_DESTROYING : WSTATE_UNMAPPING; @@ -1484,7 +1775,8 @@ void unmap_win(session_t *ps, win **_w, bool destroy) { return; } - log_trace("Unmapping %#010x \"%s\", destroy = %d", w->id, (w ? w->name : NULL), destroy); + log_trace("Unmapping %#010x \"%s\", destroy = %d", w->base.id, + (w ? w->name : NULL), destroy); if (unlikely(w->state == WSTATE_DESTROYING && !destroy)) { log_warn("Trying to undestroy a window?"); @@ -1497,10 +1789,19 @@ void unmap_win(session_t *ps, win **_w, bool destroy) { return; } + if (destroy) { + // Delete destroyed window from the hash table, so future window with the + // same window id won't confuse us. + // Keep the window in the window stack, since we might still need to + // render it (fading out). Window will be removed from the stack when + // fading finishes. + HASH_DEL(ps->windows, &w->base); + } + if (unlikely(w->state == WSTATE_UNMAPPED) || w->a._class == XCB_WINDOW_CLASS_INPUT_ONLY) { if (unlikely(!destroy)) { log_warn("Unmapping an already unmapped window %#010x %s twice", - w->id, w->name); + w->base.id, w->name); return; } // Window is already unmapped, or is an Input Only window, just destroy it @@ -1508,8 +1809,8 @@ void unmap_win(session_t *ps, win **_w, bool destroy) { return; } - // Set focus out - win_set_focused(ps, w, false); + // Note we don't update focused window here. This will either be + // triggered by subsequence Focus{In, Out} event, or by recheck_focus w->a.map_state = XCB_MAP_STATE_UNMAPPED; w->state = target_state; @@ -1519,16 +1820,16 @@ void unmap_win(session_t *ps, win **_w, bool destroy) { // don't care about properties anymore if (!destroy) { - win_ev_stop(ps, w); + win_ev_stop(ps, &w->base); } #ifdef CONFIG_DBUS // Send D-Bus signal if (ps->o.dbus) { if (destroy) { - cdbus_ev_win_destroyed(ps, w); + cdbus_ev_win_destroyed(ps, &w->base); } else { - cdbus_ev_win_unmapped(ps, w); + cdbus_ev_win_unmapped(ps, &w->base); } } #endif @@ -1541,8 +1842,8 @@ void unmap_win(session_t *ps, win **_w, bool destroy) { /** * Execute fade callback of a window if fading finished. */ -void win_check_fade_finished(session_t *ps, win **_w) { - win *w = *_w; +void win_check_fade_finished(session_t *ps, struct managed_win **_w) { + auto w = *_w; if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) { // No fading in progress assert(w->opacity_tgt == w->opacity); @@ -1561,12 +1862,13 @@ void win_check_fade_finished(session_t *ps, win **_w) { /// Skip the current in progress fading of window, /// transition the window straight to its end state -void win_skip_fading(session_t *ps, win **_w) { - win *w = *_w; +void win_skip_fading(session_t *ps, struct managed_win **_w) { + auto w = *_w; if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) { assert(w->opacity_tgt == w->opacity); return; } + log_trace("Skipping fading process of window %#010x (%s)", w->base.id, w->name); w->opacity = w->opacity_tgt; win_check_fade_finished(ps, _w); } @@ -1579,31 +1881,21 @@ void win_skip_fading(session_t *ps, win **_w) { * TODO move to x.c * TODO use xrandr */ -void win_update_screen(session_t *ps, win *w) { +void win_update_screen(session_t *ps, struct managed_win *w) { w->xinerama_scr = -1; - if (!ps->xinerama_scrs) - return; - - xcb_xinerama_screen_info_t *scrs = - xcb_xinerama_query_screens_screen_info(ps->xinerama_scrs); - int length = xcb_xinerama_query_screens_screen_info_length(ps->xinerama_scrs); - for (int i = 0; i < length; i++) { - xcb_xinerama_screen_info_t *s = &scrs[i]; - if (s->x_org <= w->g.x && s->y_org <= w->g.y && - s->x_org + s->width >= w->g.x + w->widthb && - s->y_org + s->height >= w->g.y + w->heightb) { + for (int i = 0; i < ps->xinerama_nscrs; i++) { + auto e = pixman_region32_extents(&ps->xinerama_scr_regs[i]); + if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb && + e->y2 >= w->g.y + w->heightb) { w->xinerama_scr = i; return; } } } -// TODO remove this -void configure_win(session_t *, xcb_configure_notify_event_t *); - /// Map an already registered window -void map_win(session_t *ps, win *w) { +void map_win(session_t *ps, struct managed_win *w) { assert(w); // Don't care about window mapping if it's an InputOnly window @@ -1612,15 +1904,44 @@ void map_win(session_t *ps, win *w) { return; } - log_debug("Mapping (%#010x \"%s\")", w->id, w->name); + log_debug("Mapping (%#010x \"%s\")", w->base.id, w->name); + assert(w->state != WSTATE_DESTROYING); if (w->state != WSTATE_UNMAPPED && w->state != WSTATE_UNMAPPING) { log_warn("Mapping an already mapped window"); return; } - // XXX ??? - assert(!win_is_focused_real(ps, w)); + if (w->state == WSTATE_UNMAPPING) { + win_skip_fading(ps, &w); + // We skipped the unmapping process, the window was rendered, now it is + // not anymore. So we need to mark then unmapping window as damaged. + add_damage_from_win(ps, w); + assert(w); + } + + // We stopped processing window size change when we were unmapped, refresh the + // size of the window + xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(ps->c, w->base.id); + xcb_get_geometry_reply_t *g = xcb_get_geometry_reply(ps->c, gcookie, NULL); + + if (!g) { + log_error("Failed to get the geometry of window %#010x", w->base.id); + return; + } + + w->g = *g; + free(g); + + win_on_win_size_change(ps, w); + log_trace("Window size: %dx%d", w->g.width, w->g.height); + + // Rant: window size could change after we queried its geometry here and before + // we get its pixmap. Later, when we get back to the event processing loop, we + // will get the notification about size change from Xserver and try to refresh the + // pixmap, while the pixmap is actually already up-to-date (i.e. the notification + // is stale). There is basically no real way to prevent this, aside from grabbing + // the server. // XXX Can we assume map_state is always viewable? w->a.map_state = XCB_MAP_STATE_VIEWABLE; @@ -1630,12 +1951,12 @@ void map_win(session_t *ps, win *w) { // Set window event mask before reading properties so that no property // changes are lost xcb_change_window_attributes( - ps->c, w->id, XCB_CW_EVENT_MASK, - (const uint32_t[]){determine_evmask(ps, w->id, WIN_EVMODE_FRAME)}); + ps->c, w->base.id, XCB_CW_EVENT_MASK, + (const uint32_t[]){determine_evmask(ps, w->base.id, WIN_EVMODE_FRAME)}); // Notify compton when the shape of a window changes if (ps->shape_exists) { - xcb_shape_select_input(ps->c, w->id, 1); + xcb_shape_select_input(ps->c, w->base.id, 1); } // Update window mode here to check for ARGB windows @@ -1651,7 +1972,7 @@ void map_win(session_t *ps, win *w) { } assert(w->client_win); - log_debug("Window (%#010x) has type %s", w->id, WINTYPES[w->window_type]); + log_debug("Window (%#010x) has type %s", w->base.id, WINTYPES[w->window_type]); // Update window focus state win_update_focused(ps, w); @@ -1672,44 +1993,37 @@ void map_win(session_t *ps, win *w) { w->state = WSTATE_MAPPING; w->opacity_tgt = win_calc_opacity_target(ps, w); - // TODO win_update_bounding_shape below will immediately - // reinit w->win_data, not very efficient - if (ps->redirected && ps->o.experimental_backends) { - auto pixmap = xcb_generate_id(ps->c); - xcb_composite_name_window_pixmap(ps->c, w->id, pixmap); - w->win_image = ps->backend_data->ops->bind_pixmap( - ps->backend_data, pixmap, x_get_visual_info(ps->c, w->a.visual), true); - if (w->shadow) { - w->shadow_image = ps->backend_data->ops->render_shadow( - ps->backend_data, w->widthb, w->heightb, ps->gaussian_map, - ps->o.shadow_red, ps->o.shadow_green, ps->o.shadow_blue, - ps->o.shadow_opacity); - } - } - log_debug("Window %#010x has opacity %f, opacity target is %f", w->id, w->opacity, - w->opacity_tgt); + log_debug("Window %#010x has opacity %f, opacity target is %f", w->base.id, + w->opacity, w->opacity_tgt); win_determine_blur_background(ps, w); w->ever_damaged = false; - /* if any configure events happened while - the window was unmapped, then configure - the window to its correct place */ - if (w->need_configure) { - configure_win(ps, &w->queue_configure); - } - // We stopped listening on ShapeNotify events // when the window is unmapped (XXX we shouldn't), // so the shape of the window might have changed, // update. (Issue #35) win_update_bounding_shape(ps, w); + // Reset the STALE_IMAGE flag set by win_update_bounding_shape. Because we are + // just about to bind the image, no way that's stale. + // + // Also because NVIDIA driver doesn't like seeing the same pixmap under different + // ids, so avoid naming the pixmap again when it didn't actually change. + w->flags &= ~WIN_FLAGS_IMAGE_STALE; + + // Bind image after update_bounding_shape, so the shadow has the correct size. + if (ps->backend_data) { + if (!win_bind_image(ps, w)) { + w->flags |= WIN_FLAGS_IMAGE_ERROR; + } + } + #ifdef CONFIG_DBUS // Send D-Bus signal if (ps->o.dbus) { - cdbus_ev_win_mapped(ps, w); + cdbus_ev_win_mapped(ps, &w->base); } #endif @@ -1733,7 +2047,7 @@ void map_win_by_id(session_t *ps, xcb_window_t id) { return; } - win *w = find_win(ps, id); + auto w = find_managed_win(ps, id); if (!w) { return; } @@ -1741,4 +2055,128 @@ void map_win_by_id(session_t *ps, xcb_window_t id) { map_win(ps, w); } -// vim: set et sw=2 : +/** + * Find a managed window from window id in window linked list of the session. + */ +struct win *find_win(session_t *ps, xcb_window_t id) { + if (!id) { + return NULL; + } + + struct win *w = NULL; + HASH_FIND_INT(ps->windows, &id, w); + assert(w == NULL || !w->destroyed); + return w; +} + +/** + * Find a managed window from window id in window linked list of the session. + */ +struct managed_win *find_managed_win(session_t *ps, xcb_window_t id) { + struct win *w = find_win(ps, id); + if (!w || !w->managed) { + return NULL; + } + + auto mw = (struct managed_win *)w; + assert(mw->state != WSTATE_DESTROYING); + return mw; +} + +/** + * Find out the WM frame of a client window using existing data. + * + * @param id window ID + * @return struct win object of the found window, NULL if not found + */ +struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) { + if (!id) { + return NULL; + } + + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + if (!w->managed) { + continue; + } + + auto mw = (struct managed_win *)w; + if (mw->client_win == id) { + return mw; + } + } + + return NULL; +} + +/** + * Find out the WM frame of a client window by querying X. + * + * @param ps current session + * @param wid window ID + * @return struct _win object of the found window, NULL if not found + */ +struct managed_win *find_toplevel2(session_t *ps, xcb_window_t wid) { + // TODO this should probably be an "update tree", then find_toplevel. + // current approach is a bit more "racy" + struct win *w = NULL; + + // We traverse through its ancestors to find out the frame + // Using find_win here because if we found a unmanaged window we know about, we + // can stop early. + while (wid && wid != ps->root && !(w = find_win(ps, wid))) { + // xcb_query_tree probably fails if you run compton when X is somehow + // initializing (like add it in .xinitrc). In this case + // just leave it alone. + auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL); + if (reply == NULL) { + break; + } + + wid = reply->parent; + free(reply); + } + + if (w == NULL || !w->managed) { + return NULL; + } + + return (struct managed_win *)w; +} + +/** + * Check if a rectangle includes the whole screen. + */ +static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid, int hei) { + return (x <= 0 && y <= 0 && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height); +} + +/** + * Check if a window is a fullscreen window. + * + * It's not using w->border_size for performance measures. + */ +bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) { + return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) && + (!w->bounding_shaped || w->rounded_corners); +} + +/** + * Check if a window is really focused. + */ +bool win_is_focused_real(const session_t *ps, const struct managed_win *w) { + return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w; +} + +// Find the managed window immediately below `i` in the window stack +struct managed_win * +win_stack_find_next_managed(const session_t *ps, const struct list_node *i) { + while (!list_node_is_last(&ps->window_stack, i)) { + auto next = list_entry(i->next, struct win, stack_neighbour); + if (next->managed) { + return (struct managed_win *)next; + } + i = &next->stack_neighbour; + } + return NULL; +} diff --git a/src/win.h b/src/win.h index dcaf422800..ff97617e84 100644 --- a/src/win.h +++ b/src/win.h @@ -7,13 +7,17 @@ #include #include +#include "uthash_extra.h" + // FIXME shouldn't need this #ifdef CONFIG_OPENGL #include #endif +#include "backend/backend.h" #include "c2.h" #include "compiler.h" +#include "list.h" #include "region.h" #include "render.h" #include "types.h" @@ -23,6 +27,13 @@ typedef struct session session_t; typedef struct _glx_texture glx_texture_t; +#define win_stack_foreach_managed(w, win_stack) \ + list_foreach(struct managed_win, w, win_stack, base.stack_neighbour) if (w->base.managed) + +#define win_stack_foreach_managed_safe(w, win_stack) \ + list_foreach_safe(struct managed_win, w, win_stack, \ + base.stack_neighbour) if (w->base.managed) + #ifdef CONFIG_OPENGL // FIXME this type should be in opengl.h // it is very unideal for it to be here @@ -51,7 +62,7 @@ typedef enum { WINTYPE_DROPDOWN_MENU, WINTYPE_POPUP_MENU, WINTYPE_TOOLTIP, - WINTYPE_NOTIFY, + WINTYPE_NOTIFICATION, WINTYPE_COMBO, WINTYPE_DND, NUM_WINTYPES @@ -104,6 +115,25 @@ typedef enum { WSTATE_UNMAPPED, } winstate_t; +enum win_flags { + /// win_image/shadow_image is out of date + WIN_FLAGS_IMAGE_STALE = 1, + /// there was an error trying to bind the images + WIN_FLAGS_IMAGE_ERROR = 2, +}; + +/// An entry in the window stack. May or may not correspond to a window we know about. +struct window_stack_entry { + struct list_node stack_neighbour; + /// The actual window correspond to this stack entry. NULL if we didn't know about + /// this window (e.g. an InputOnly window, or we haven't handled the window + /// creation yet) + struct win *win; + /// The window id. Might not be unique in the stack, because there might be + /// destroyed window still fading out in the stack. + xcb_window_t id; +}; + /** * About coordinate systems * @@ -118,19 +148,30 @@ typedef enum { /// Structure representing a top-level window compton manages. typedef struct win win; struct win { + UT_hash_handle hh; + struct list_node stack_neighbour; + /// ID of the top-level frame window. + xcb_window_t id; + /// Whether the window is destroyed from Xorg's perspective + bool destroyed : 1; + /// True if we just received CreateNotify, and haven't queried X for any info + /// about the window + bool is_new : 1; + /// True if this window is managed, i.e. this struct is actually a `managed_win`. + /// Always false if `is_new` is true. + bool managed : 1; +}; +struct managed_win { + struct win base; /// backend data attached to this window. Only available when /// `state` is not UNMAPPED void *win_image; void *shadow_image; - /// Pointer to the next lower window in window stack. - win *next; /// Pointer to the next higher window to paint. - win *prev_trans; + struct managed_win *prev_trans; // TODO rethink reg_ignore // Core members - /// ID of the top-level frame window. - xcb_window_t id; /// The "mapped state" of this window, doesn't necessary /// match X mapped state, because of fading. winstate_t state; @@ -139,8 +180,10 @@ struct win { xcb_get_geometry_reply_t g; /// Xinerama screen this window is on. int xinerama_scr; - /// Window visual pict format; + /// Window visual pict format const xcb_render_pictforminfo_t *pictfmt; + /// Client window visual pict format + const xcb_render_pictforminfo_t *client_pictfmt; /// Window painting mode. winmode_t mode; /// Whether the window has been damaged at least once. @@ -157,9 +200,6 @@ struct win { region_t bounding_shape; /// Window flags. Definitions above. int_fast16_t flags; - /// Whether there's a pending ConfigureNotify happening - /// when the window is unmapped. - bool need_configure; /// Queued ConfigureNotify when the window is unmapped. xcb_configure_notify_event_t queue_configure; /// The region of screen that will be obscured when windows above is painted, @@ -282,56 +322,64 @@ struct win { #endif }; -int win_get_name(session_t *ps, win *w); -int win_get_role(session_t *ps, win *w); -winmode_t attr_pure win_calc_mode(const win *w); +void win_release_image(backend_t *base, struct managed_win *w); +bool must_use win_bind_image(session_t *ps, struct managed_win *w); + +/// Attempt a rebind of window's images. If that failed, the original images are kept. +bool must_use win_try_rebind_image(session_t *ps, struct managed_win *w); +int win_get_name(session_t *ps, struct managed_win *w); +int win_get_role(session_t *ps, struct managed_win *w); +winmode_t attr_pure win_calc_mode(const struct managed_win *w); +void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val); +void win_set_fade_force(session_t *ps, struct managed_win *w, switch_t val); +void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val); +void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t val); /** * Set real focused state of a window. */ -void win_set_focused(session_t *ps, win *w, bool focused); -bool attr_pure win_should_fade(session_t *ps, const win *w); -void win_update_prop_shadow_raw(session_t *ps, win *w); -void win_update_prop_shadow(session_t *ps, win *w); -void win_set_shadow(session_t *ps, win *w, bool shadow_new); -void win_determine_shadow(session_t *ps, win *w); -void win_set_invert_color(session_t *ps, win *w, bool invert_color_new); -void win_determine_invert_color(session_t *ps, win *w); -void win_set_blur_background(session_t *ps, win *w, bool blur_background_new); -void win_determine_blur_background(session_t *ps, win *w); -void win_on_wtype_change(session_t *ps, win *w); -void win_on_factor_change(session_t *ps, win *w); +void win_set_focused(session_t *ps, struct managed_win *w); +bool attr_pure win_should_fade(session_t *ps, const struct managed_win *w); +void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w); +void win_update_prop_shadow(session_t *ps, struct managed_win *w); +void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new); +void win_determine_shadow(session_t *ps, struct managed_win *w); +void win_set_invert_color(session_t *ps, struct managed_win *w, bool invert_color_new); +void win_determine_invert_color(session_t *ps, struct managed_win *w); +void win_determine_blur_background(session_t *ps, struct managed_win *w); +void win_on_wtype_change(session_t *ps, struct managed_win *w); +void win_on_factor_change(session_t *ps, struct managed_win *w); /** * Update cache data in struct _win that depends on window size. */ -void win_on_win_size_change(session_t *ps, win *w); -void win_update_wintype(session_t *ps, win *w); -void win_mark_client(session_t *ps, win *w, xcb_window_t client); -void win_unmark_client(session_t *ps, win *w); -void win_recheck_client(session_t *ps, win *w); -xcb_window_t win_get_leader_raw(session_t *ps, win *w, int recursions); -bool win_get_class(session_t *ps, win *w); -double attr_pure win_calc_opacity_target(session_t *ps, const win *w); -bool attr_pure win_should_dim(session_t *ps, const win *w); -void win_update_screen(session_t *, win *); +void win_on_win_size_change(session_t *ps, struct managed_win *w); +void win_update_wintype(session_t *ps, struct managed_win *w); +void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client); +void win_unmark_client(session_t *ps, struct managed_win *w); +void win_recheck_client(session_t *ps, struct managed_win *w); +xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions); +bool win_get_class(session_t *ps, struct managed_win *w); +double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w); +bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w); +void win_update_screen(session_t *, struct managed_win *); /// Prepare window for fading because opacity target changed -void win_start_fade(session_t *, win **); +void win_start_fade(session_t *, struct managed_win **); /** * Reread opacity property of a window. */ -void win_update_opacity_prop(session_t *ps, win *w); +void win_update_opacity_prop(session_t *ps, struct managed_win *w); /** * Update leader of a window. */ -void win_update_leader(session_t *ps, win *w); +void win_update_leader(session_t *ps, struct managed_win *w); /** * Update focused state of a window. */ -void win_update_focused(session_t *ps, win *w); +void win_update_focused(session_t *ps, struct managed_win *w); /** * Retrieve the bounding shape of a window. */ // XXX was win_border_size -void win_update_bounding_shape(session_t *ps, win *w); +void win_update_bounding_shape(session_t *ps, struct managed_win *w); /** * Get a rectangular region in global coordinates a window (and possibly * its shadow) occupies. @@ -339,69 +387,113 @@ void win_update_bounding_shape(session_t *ps, win *w); * Note w->shadow and shadow geometry must be correct before calling this * function. */ -void win_extents(const win *w, region_t *res); -region_t win_extents_by_val(const win *w); +void win_extents(const struct managed_win *w, region_t *res); +region_t win_extents_by_val(const struct managed_win *w); /** * Add a window to damaged area. * * @param ps current session * @param w struct _win element representing the window */ -void add_damage_from_win(session_t *ps, win *w); +void add_damage_from_win(session_t *ps, const struct managed_win *w); /** * Get a rectangular region a window occupies, excluding frame and shadow. * * Return region in global coordinates. */ -void win_get_region_noframe_local(const win *w, region_t *); -region_t win_get_region_noframe_local_by_val(const win *w); +void win_get_region_noframe_local(const struct managed_win *w, region_t *); +region_t win_get_region_noframe_local_by_val(const struct managed_win *w); /// Get the region for the frame of the window -void win_get_region_frame_local(const win *w, region_t *res); +void win_get_region_frame_local(const struct managed_win *w, region_t *res); /// Get the region for the frame of the window, by value -region_t win_get_region_frame_local_by_val(const win *w); +region_t win_get_region_frame_local_by_val(const struct managed_win *w); /** * Retrieve frame extents from a window. */ -void win_update_frame_extents(session_t *ps, win *w, xcb_window_t client); -void add_win(session_t *ps, xcb_window_t id, xcb_window_t prev); +void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client); +/// Insert a new window above window with id `below`, if there is no window, add to top +/// New window will be in unmapped state +struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below); +/// Insert a new win entry at the top of the stack +struct win *add_win_top(session_t *ps, xcb_window_t id); +/// Query the Xorg for information about window `win` +/// `win` pointer might become invalid after this function returns +struct win *fill_win(session_t *ps, struct win *win); +/// Move window `w` to be right above `below` +void restack_above(session_t *ps, struct win *w, xcb_window_t below); +/// Move window `w` to the bottom of the stack +void restack_bottom(session_t *ps, struct win *w); +/// Move window `w` to the top of the stack +void restack_top(session_t *ps, struct win *w); /// Unmap or destroy a window -void unmap_win(session_t *ps, win **, bool destroy); +void unmap_win(session_t *ps, struct managed_win **, bool destroy); +/// Destroy an unmanaged window +void destroy_unmanaged_win(session_t *ps, struct win **w); -void map_win(session_t *ps, win *w); +void map_win(session_t *ps, struct managed_win *w); void map_win_by_id(session_t *ps, xcb_window_t id); /** * Execute fade callback of a window if fading finished. */ -void win_check_fade_finished(session_t *ps, win **_w); +void win_check_fade_finished(session_t *ps, struct managed_win **_w); // Stop receiving events (except ConfigureNotify, XXX why?) from a window -void win_ev_stop(session_t *ps, const win *w); +void win_ev_stop(session_t *ps, const struct win *w); /// Skip the current in progress fading of window, /// transition the window straight to its end state -void win_skip_fading(session_t *ps, win **_w); +void win_skip_fading(session_t *ps, struct managed_win **_w); +/** + * Find a managed window from window id in window linked list of the session. + */ +struct managed_win *find_managed_win(session_t *ps, xcb_window_t id); +struct win *find_win(session_t *ps, xcb_window_t id); +struct managed_win *find_toplevel(session_t *ps, xcb_window_t id); +/** + * Find out the WM frame of a client window by querying X. + * + * @param ps current session + * @param wid window ID + * @return struct _win object of the found window, NULL if not found + */ +struct managed_win *find_toplevel2(session_t *ps, xcb_window_t wid); + +/** + * Check if a window is a fullscreen window. + * + * It's not using w->border_size for performance measures. + */ +bool attr_pure win_is_fullscreen(const session_t *ps, const struct managed_win *w); +/** + * Check if a window is really focused. + */ +bool attr_pure win_is_focused_real(const session_t *ps, const struct managed_win *w); /** * Get the leader of a window. * * This function updates w->cache_leader if necessary. */ -static inline xcb_window_t win_get_leader(session_t *ps, win *w) { +static inline xcb_window_t win_get_leader(session_t *ps, struct managed_win *w) { return win_get_leader_raw(ps, w, 0); } /// check if window has ARGB visual -bool attr_pure win_has_alpha(const win *w); +bool attr_pure win_has_alpha(const struct managed_win *w); /// check if reg_ignore_valid is true for all windows above us -bool attr_pure win_is_region_ignore_valid(session_t *ps, const win *w); +bool attr_pure win_is_region_ignore_valid(session_t *ps, const struct managed_win *w); + +// Find the managed window immediately below `w` in the window stack +struct managed_win *attr_pure win_stack_find_next_managed(const session_t *ps, + const struct list_node *w); /// Free all resources in a struct win -void free_win_res(session_t *ps, win *w); +void free_win_res(session_t *ps, struct managed_win *w); -static inline region_t win_get_bounding_shape_global_by_val(win *w) { +static inline region_t win_get_bounding_shape_global_by_val(struct managed_win *w) { region_t ret; pixman_region32_init(&ret); pixman_region32_copy(&ret, &w->bounding_shape); @@ -413,19 +505,27 @@ static inline region_t win_get_bounding_shape_global_by_val(win *w) { * Calculate the extents of the frame of the given window based on EWMH * _NET_FRAME_EXTENTS and the X window border width. */ -static inline margin_t attr_pure win_calc_frame_extents(const win *w) { +static inline margin_t attr_pure win_calc_frame_extents(const struct managed_win *w) { margin_t result = w->frame_extents; - result.top = max_i(result.top, w->g.border_width); - result.left = max_i(result.left, w->g.border_width); - result.bottom = max_i(result.bottom, w->g.border_width); - result.right = max_i(result.right, w->g.border_width); + result.top = max2(result.top, w->g.border_width); + result.left = max2(result.left, w->g.border_width); + result.bottom = max2(result.bottom, w->g.border_width); + result.right = max2(result.right, w->g.border_width); return result; } /** * Check whether a window has WM frames. */ -static inline bool attr_pure win_has_frame(const win *w) { +static inline bool attr_pure win_has_frame(const struct managed_win *w) { return w->g.border_width || w->frame_extents.top || w->frame_extents.left || w->frame_extents.right || w->frame_extents.bottom; } + +static inline const char *win_get_name_if_managed(const struct win *w) { + if (!w->managed) { + return "(unmanaged)"; + } + auto mw = (struct managed_win *)w; + return mw->name; +} diff --git a/src/x.c b/src/x.c index 857dd5439e..ed6668a88b 100644 --- a/src/x.c +++ b/src/x.c @@ -13,6 +13,7 @@ #include #include +#include "atom.h" #include "backend/gl/glx.h" #include "common.h" #include "compiler.h" @@ -38,18 +39,21 @@ * and number of items. A blank one on failure. */ winprop_t wid_get_prop_adv(const session_t *ps, xcb_window_t w, xcb_atom_t atom, - long offset, long length, xcb_atom_t rtype, int rformat) { + int offset, int length, xcb_atom_t rtype, int rformat) { xcb_get_property_reply_t *r = xcb_get_property_reply( - ps->c, xcb_get_property(ps->c, 0, w, atom, rtype, offset, length), NULL); + ps->c, + xcb_get_property(ps->c, 0, w, atom, rtype, to_u32_checked(offset), + to_u32_checked(length)), + NULL); if (r && xcb_get_property_value_length(r) && (rtype == XCB_GET_PROPERTY_TYPE_ANY || r->type == rtype) && (!rformat || r->format == rformat) && (r->format == 8 || r->format == 16 || r->format == 32)) { - int len = xcb_get_property_value_length(r); + auto len = xcb_get_property_value_length(r); return (winprop_t){ .ptr = xcb_get_property_value(r), - .nitems = len / (r->format / 8), + .nitems = (ulong)(len / (r->format / 8)), .type = r->type, .format = r->format, .r = r, @@ -73,7 +77,7 @@ xcb_window_t wid_get_prop_window(session_t *ps, xcb_window_t wid, xcb_atom_t apr // Return it if (prop.nitems) { - p = *prop.p32; + p = (xcb_window_t)*prop.p32; } free_winprop(&prop); @@ -96,11 +100,11 @@ bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ** *pnstr = 0; if (*pstrlst) XFreeStringList(*pstrlst); - cxfree(text_prop.value); + XFree(text_prop.value); return false; } - cxfree(text_prop.value); + XFree(text_prop.value); return true; } @@ -182,7 +186,7 @@ int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) { xcb_render_picture_t x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, const xcb_render_pictforminfo_t *pictfmt, - xcb_pixmap_t pixmap, unsigned long valuemask, + xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { void *buf = NULL; if (attr) { @@ -193,7 +197,7 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, } } - xcb_render_picture_t tmp_picture = xcb_generate_id(c); + xcb_render_picture_t tmp_picture = x_new_id(c); xcb_generic_error_t *e = xcb_request_check(c, xcb_render_create_picture_checked( c, tmp_picture, pixmap, pictfmt->id, valuemask, buf)); @@ -208,7 +212,7 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, xcb_render_picture_t x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visual, - xcb_pixmap_t pixmap, unsigned long valuemask, + xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { const xcb_render_pictforminfo_t *pictfmt = x_get_pictform_for_visual(c, visual); return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr); @@ -216,7 +220,7 @@ x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visu xcb_render_picture_t x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard_t standard, - xcb_pixmap_t pixmap, unsigned long valuemask, + xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { x_get_server_pictfmts(c); @@ -229,12 +233,12 @@ x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard * Create an picture. */ xcb_render_picture_t -x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int wid, int hei, - const xcb_render_pictforminfo_t *pictfmt, unsigned long valuemask, +x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int w, int h, + const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { - int depth = pictfmt->depth; + uint8_t depth = pictfmt->depth; - xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, d, wid, hei); + xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, d, w, h); if (!tmp_pixmap) return XCB_NONE; @@ -248,7 +252,7 @@ x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int wid, in xcb_render_picture_t x_create_picture_with_visual(xcb_connection_t *c, xcb_drawable_t d, int w, int h, - xcb_visualid_t visual, unsigned long valuemask, + xcb_visualid_t visual, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) { auto pictfmt = x_get_pictform_for_visual(c, visual); return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr); @@ -279,21 +283,22 @@ bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_ } void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, - int clip_x_origin, int clip_y_origin, const region_t *reg) { + int16_t clip_x_origin, int16_t clip_y_origin, + const region_t *reg) { int nrects; const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects); auto xrects = ccalloc(nrects, xcb_rectangle_t); for (int i = 0; i < nrects; i++) xrects[i] = (xcb_rectangle_t){ - .x = rects[i].x1, - .y = rects[i].y1, - .width = rects[i].x2 - rects[i].x1, - .height = rects[i].y2 - rects[i].y1, + .x = to_i16_checked(rects[i].x1), + .y = to_i16_checked(rects[i].y1), + .width = to_u16_checked(rects[i].x2 - rects[i].x1), + .height = to_u16_checked(rects[i].y2 - rects[i].y1), }; - xcb_generic_error_t *e = - xcb_request_check(c, xcb_render_set_picture_clip_rectangles_checked( - c, pict, clip_x_origin, clip_y_origin, nrects, xrects)); + xcb_generic_error_t *e = xcb_request_check( + c, xcb_render_set_picture_clip_rectangles_checked( + c, pict, clip_x_origin, clip_y_origin, to_u32_checked(nrects), xrects)); if (e) log_error("Failed to set clip region"); free(e); @@ -321,7 +326,7 @@ enum { XSyncBadCounter = 0, * * XXX consider making this error to string */ -void x_print_error(unsigned long serial, uint8_t major, uint8_t minor, uint8_t error_code) { +void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) { session_t *const ps = ps_g; int o = 0; @@ -405,10 +410,10 @@ void x_print_error(unsigned long serial, uint8_t major, uint8_t minor, uint8_t e * Create a pixmap and check that creation succeeded. */ xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t drawable, - uint16_t width, uint16_t height) { - xcb_pixmap_t pix = xcb_generate_id(c); - xcb_void_cookie_t cookie = - xcb_create_pixmap_checked(c, depth, pix, drawable, width, height); + int width, int height) { + xcb_pixmap_t pix = x_new_id(c); + xcb_void_cookie_t cookie = xcb_create_pixmap_checked( + c, depth, pix, drawable, to_u16_checked(width), to_u16_checked(height)); xcb_generic_error_t *err = xcb_request_check(c, cookie); if (err == NULL) return pix; @@ -452,11 +457,11 @@ xcb_pixmap_t x_get_root_back_pixmap(session_t *ps) { // Get the values of background attributes for (int p = 0; background_props_str[p]; p++) { - xcb_atom_t prop_atom = get_atom(ps, background_props_str[p]); + xcb_atom_t prop_atom = get_atom(ps->atoms, background_props_str[p]); winprop_t prop = wid_get_prop(ps, ps->root, prop_atom, 1, XCB_ATOM_PIXMAP, 32); if (prop.nitems) { - pixmap = *prop.p32; + pixmap = (xcb_pixmap_t)*prop.p32; free_winprop(&prop); break; } @@ -468,7 +473,7 @@ xcb_pixmap_t x_get_root_back_pixmap(session_t *ps) { bool x_is_root_back_pixmap_atom(session_t *ps, xcb_atom_t atom) { for (int p = 0; background_props_str[p]; p++) { - xcb_atom_t prop_atom = get_atom(ps, background_props_str[p]); + xcb_atom_t prop_atom = get_atom(ps->atoms, background_props_str[p]); if (prop_atom == atom) return true; } @@ -523,17 +528,28 @@ bool x_fence_sync(xcb_connection_t *c, xcb_sync_fence_t f) { * @param[inout] size size of the array pointed to by `ret`, in number of elements * @return number of elements filled into `*ret` */ -size_t x_picture_filter_from_conv(const conv *kernel, double center, - xcb_render_fixed_t **ret, size_t *size) { - if (*size < (size_t)(kernel->w * kernel->h + 2)) { - *size = kernel->w * kernel->h + 2; - *ret = crealloc(*ret, *size); +void x_create_convolution_kernel(const conv *kernel, double center, + struct x_convolution_kernel **ret) { + assert(ret); + if (!*ret || (*ret)->capacity < kernel->w * kernel->h + 2) { + free(*ret); + *ret = + cvalloc(sizeof(struct x_convolution_kernel) + + (size_t)(kernel->w * kernel->h + 2) * sizeof(xcb_render_fixed_t)); + (*ret)->capacity = kernel->w * kernel->h + 2; } - auto buf = *ret; + + (*ret)->size = kernel->w * kernel->h + 2; + + auto buf = (*ret)->kernel; buf[0] = DOUBLE_TO_XFIXED(kernel->w); buf[1] = DOUBLE_TO_XFIXED(kernel->h); + double sum = center; for (int i = 0; i < kernel->w * kernel->h; i++) { + if (i == kernel->w * kernel->h / 2) { + continue; + } sum += kernel->data[i]; } @@ -546,22 +562,21 @@ size_t x_picture_filter_from_conv(const conv *kernel, double center, buf[kernel->h / 2 * kernel->w + kernel->w / 2 + 2] = DOUBLE_TO_XFIXED(center * factor); - return kernel->w * kernel->h + 2; } /// Generate a search criteria for fbconfig from a X visual. -/// Returns {-1, -1, -1, -1, -1, -1} on failure +/// Returns {-1, -1, -1, -1, -1, 0} on failure struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual) { auto pictfmt = x_get_pictform_for_visual(c, visual); auto depth = x_get_visual_depth(c, visual); if (!pictfmt || depth == -1) { log_error("Invalid visual %#03x", visual); - return (struct xvisual_info){-1, -1, -1, -1, -1, -1}; + return (struct xvisual_info){-1, -1, -1, -1, -1, 0}; } if (pictfmt->type != XCB_RENDER_PICT_TYPE_DIRECT) { log_error("compton cannot handle non-DirectColor visuals. Report an " "issue if you see this error message."); - return (struct xvisual_info){-1, -1, -1, -1, -1, -1}; + return (struct xvisual_info){-1, -1, -1, -1, -1, 0}; } int red_size = popcountl(pictfmt->direct.red_mask), @@ -578,3 +593,14 @@ struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual .visual = visual, }; } + +xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen) { + xcb_screen_iterator_t iter; + + iter = xcb_setup_roots_iterator(xcb_get_setup(c)); + for (; iter.rem; --screen, xcb_screen_next(&iter)) + if (screen == 0) + return iter.data; + + return NULL; +} diff --git a/src/x.h b/src/x.h index 2797e8142d..72e0dfea08 100644 --- a/src/x.h +++ b/src/x.h @@ -47,9 +47,20 @@ struct xvisual_info { xcb_visualid_t visual; }; -#define XCB_SYNCED_VOID(func, c, ...) \ - xcb_request_check(c, func##_checked(c, __VA_ARGS__)); -#define XCB_SYNCED(func, c, ...) \ +#define XCB_AWAIT_VOID(func, c, ...) \ + ({ \ + bool success = true; \ + __auto_type e = xcb_request_check(c, func##_checked(c, __VA_ARGS__)); \ + if (e) { \ + x_print_error(e->sequence, e->major_code, e->minor_code, \ + e->error_code); \ + free(e); \ + success = false; \ + } \ + success; \ + }) + +#define XCB_AWAIT(func, c, ...) \ ({ \ xcb_generic_error_t *e = NULL; \ __auto_type r = func##_reply(c, func(c, __VA_ARGS__), &e); \ @@ -61,6 +72,18 @@ struct xvisual_info { r; \ }) +/// Wraps x_new_id. abort the program if x_new_id returns error +static inline uint32_t x_new_id(xcb_connection_t *c) { + auto ret = xcb_generate_id(c); + if (ret == (uint32_t)-1) { + log_fatal("We seems to have run of XIDs. This is either a bug in the X " + "server, or a resource leakage in compton. Please open an " + "issue about this problem. compton will die."); + abort(); + } + return ret; +} + /** * Send a request to X server and get the reply to make sure all previous * requests are processed, and their replies received @@ -88,16 +111,25 @@ static inline void x_sync(xcb_connection_t *c) { * and number of items. A blank one on failure. */ winprop_t wid_get_prop_adv(const session_t *ps, xcb_window_t w, xcb_atom_t atom, - long offset, long length, xcb_atom_t rtype, int rformat); + int offset, int length, xcb_atom_t rtype, int rformat); /** * Wrapper of wid_get_prop_adv(). */ static inline winprop_t wid_get_prop(const session_t *ps, xcb_window_t wid, xcb_atom_t atom, - long length, xcb_atom_t rtype, int rformat) { + int length, xcb_atom_t rtype, int rformat) { return wid_get_prop_adv(ps, wid, atom, 0L, length, rtype, rformat); } +/// Discard all X events in queue or in flight. Should only be used when the server is +/// grabbed +static inline void x_discard_events(xcb_connection_t *c) { + xcb_generic_event_t *e; + while ((e = xcb_poll_for_event(c))) { + free(e); + } +} + /** * Get the value of a type-xcb_window_t property of a window. * @@ -118,19 +150,19 @@ int x_get_visual_depth(xcb_connection_t *, xcb_visualid_t); xcb_render_picture_t x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *, const xcb_render_pictforminfo_t *pictfmt, - xcb_pixmap_t pixmap, unsigned long valuemask, + xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1, 2); xcb_render_picture_t x_create_picture_with_visual_and_pixmap(xcb_connection_t *, xcb_visualid_t visual, - xcb_pixmap_t pixmap, unsigned long valuemask, + xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); xcb_render_picture_t x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_t standard, - xcb_pixmap_t pixmap, unsigned long valuemask, + xcb_pixmap_t pixmap, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); @@ -138,22 +170,22 @@ x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_ * Create an picture. */ xcb_render_picture_t -x_create_picture_with_pictfmt(xcb_connection_t *, xcb_drawable_t, int wid, int hei, - const xcb_render_pictforminfo_t *pictfmt, unsigned long valuemask, +x_create_picture_with_pictfmt(xcb_connection_t *, xcb_drawable_t, int w, int h, + const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1, 5); xcb_render_picture_t x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h, - xcb_visualid_t visual, unsigned long valuemask, + xcb_visualid_t visual, uint32_t valuemask, const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); /// Fetch a X region and store it in a pixman region bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res); -void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, - int clip_x_origin, int clip_y_origin, const region_t *); +void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin, + int16_t clip_y_origin, const region_t *); void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict); @@ -162,10 +194,10 @@ void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict); * * XXX consider making this error to string */ -void x_print_error(unsigned long serial, uint8_t major, uint8_t minor, uint8_t error_code); +void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code); xcb_pixmap_t x_create_pixmap(xcb_connection_t *, uint8_t depth, xcb_drawable_t drawable, - uint16_t width, uint16_t height); + int width, int height); bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap); @@ -191,6 +223,12 @@ bool x_is_root_back_pixmap_atom(session_t *ps, xcb_atom_t atom); bool x_fence_sync(xcb_connection_t *, xcb_sync_fence_t); +struct x_convolution_kernel { + int size; + int capacity; + xcb_render_fixed_t kernel[]; +}; + /** * Convert a struct conv to a X picture convolution filter, normalizing the kernel * in the process. Allow the caller to specify the element at the center of the kernel, @@ -202,11 +240,15 @@ bool x_fence_sync(xcb_connection_t *, xcb_sync_fence_t); * will be allocated, and `*ret` will be updated. * @param[inout] size size of the array pointed to by `ret`. */ -size_t x_picture_filter_from_conv(const conv *kernel, double center, - xcb_render_fixed_t **ret, size_t *size); +void attr_nonnull(1, 3) x_create_convolution_kernel(const conv *kernel, double center, + struct x_convolution_kernel **ret); /// Generate a search criteria for fbconfig from a X visual. /// Returns {-1, -1, -1, -1, -1, -1} on failure struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual); xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); + +xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen); + +uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); diff --git a/subprojects/test.h b/subprojects/test.h new file mode 160000 index 0000000000..a84877df68 --- /dev/null +++ b/subprojects/test.h @@ -0,0 +1 @@ +Subproject commit a84877df68873f80ff3620f4993619b35b21f758