diff options
Diffstat (limited to 'twin/compton-tde')
-rw-r--r-- | twin/compton-tde/common.h | 54 | ||||
-rw-r--r-- | twin/compton-tde/compton.c | 155 | ||||
-rw-r--r-- | twin/compton-tde/compton.h | 6 | ||||
-rw-r--r-- | twin/compton-tde/opengl.c | 169 |
4 files changed, 382 insertions, 2 deletions
diff --git a/twin/compton-tde/common.h b/twin/compton-tde/common.h index acaf68651..ddb00fade 100644 --- a/twin/compton-tde/common.h +++ b/twin/compton-tde/common.h @@ -490,6 +490,17 @@ typedef struct { } glx_blur_cache_t; typedef struct { + /// Framebuffer used for greyscale conversion. + GLuint fbo; + /// Textures used for greyscale conversion. + GLuint textures[2]; + /// Width of the textures. + int width; + /// Height of the textures. + int height; +} glx_greyscale_cache_t; + +typedef struct { /// GLSL program. GLuint prog; /// Location of uniform "opacity" in window GLSL program. @@ -714,6 +725,10 @@ typedef struct _options_t { c2_lptr_t *blur_background_blacklist; /// Blur convolution kernel. XFixed *blur_kerns[MAX_BLUR_PASS]; + /// Whether to set background of semi-transparent / ARGB windows to greyscale. + bool greyscale_background; + /// Greyscale background blacklist. A linked list of conditions. + c2_lptr_t *greyscale_background_blacklist; /// 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 @@ -1047,10 +1062,12 @@ typedef struct _session_t { Atom atom_compton_shadow; /// Atom of property <code>_NET_WM_WINDOW_TYPE</code>. Atom atom_win_type; - /// Atom of property <code>_KDE_TRANSPARENT_TO_BLACK</code>. + /// Atom of property <code>_TDE_TRANSPARENT_TO_BLACK</code>. Atom atom_win_type_tde_transparent_to_black; - /// Atom of property <code>_KDE_TRANSPARENT_TO_DESKTOP</code>. + /// Atom of property <code>_TDE_TRANSPARENT_TO_DESKTOP</code>. Atom atom_win_type_tde_transparent_to_desktop; + /// Atom of property <code>_TDE_TRANSPARENCY_FILTER_GREYSCALE</code>. + Atom atom_win_type_tde_transparency_filter_greyscale; /// Array of atoms of all possible window types. Atom atoms_wintypes[NUM_WINTYPES]; /// Linked list of additional atoms to track. @@ -1241,6 +1258,11 @@ typedef struct _win { /// Background state on last paint. bool blur_background_last; + /// Whether to set window background to greyscale. + bool greyscale_background; + /// Background state on last paint. + bool greyscale_background_last; + /// Whether to show black background bool show_black_background; @@ -1250,6 +1272,9 @@ typedef struct _win { #ifdef CONFIG_VSYNC_OPENGL_GLSL /// Textures and FBO background blur use. glx_blur_cache_t glx_blur_cache; + + /// Textures and FBO greyscale background use. + glx_greyscale_cache_t glx_greyscale_cache; #endif } win; @@ -2219,6 +2244,10 @@ glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, float z, GLfloat factor, XserverRegion reg_tgt, const reg_data_t *pcache_reg); bool +glx_greyscale_dst(session_t *ps, int dx, int dy, int width, int height, float z, + XserverRegion reg_tgt, const reg_data_t *pcache_reg, glx_greyscale_cache_t *pbc); + +bool glx_render_(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, int width, int height, int z, double opacity, bool argb, bool neg, @@ -2306,6 +2335,26 @@ free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) { free_glx_fbo(ps, &pbc->fbo); free_glx_bc_resize(ps, pbc); } + +/** + * Free data in glx_greyscale_cache_t on resize. + */ +static inline void +free_glx_gc_resize(session_t *ps, glx_greyscale_cache_t *pbc) { + free_texture_r(ps, &pbc->textures[0]); + free_texture_r(ps, &pbc->textures[1]); + pbc->width = 0; + pbc->height = 0; +} + +/** + * Free a glx_greyscale_cache_t + */ +static inline void +free_glx_gc(session_t *ps, glx_greyscale_cache_t *pbc) { + free_glx_fbo(ps, &pbc->fbo); + free_glx_gc_resize(ps, pbc); +} #endif #endif @@ -2349,6 +2398,7 @@ free_win_res_glx(session_t *ps, win *w) { free_paint_glx(ps, &w->shadow_paint); #ifdef CONFIG_VSYNC_OPENGL_GLSL free_glx_bc(ps, &w->glx_blur_cache); + free_glx_gc(ps, &w->glx_greyscale_cache); #endif } diff --git a/twin/compton-tde/compton.c b/twin/compton-tde/compton.c index 36af91f5b..4a4d058fd 100644 --- a/twin/compton-tde/compton.c +++ b/twin/compton-tde/compton.c @@ -1260,6 +1260,7 @@ paint_preprocess(session_t *ps, win *list) { w->fade = w->fade_last; win_set_invert_color(ps, w, w->invert_color_last); win_set_blur_background(ps, w, w->blur_background_last); + win_set_greyscale_background(ps, w, w->greyscale_background_last); } // Update window opacity target and dim state if asked @@ -1407,6 +1408,7 @@ paint_preprocess(session_t *ps, win *list) { w->fade_last = w->fade; w->invert_color_last = w->invert_color; w->blur_background_last = w->blur_background; + w->greyscale_background_last = w->greyscale_background; } } } @@ -1546,6 +1548,53 @@ xr_blur_dst(session_t *ps, Picture tgt_buffer, return true; } +/** + * @brief Make an area on a buffer greyscale. + * + * @param ps current session + * @param tgt_buffer a buffer as both source and destination + * @param x x pos + * @param y y pos + * @param wid width + * @param hei height + * @param reg_clip a clipping region to be applied on intermediate buffers + * + * @return true if successful, false otherwise + */ +static bool +xr_greyscale_dst(session_t *ps, Picture tgt_buffer, + int x, int y, int wid, int hei, XserverRegion reg_clip) { + + // Directly copying from tgt_buffer to it does not work, so we create a + // Picture in the middle. + Picture tmp_picture = xr_build_picture(ps, wid, hei, NULL); + + if (!tmp_picture) { + printf_errf("(): Failed to build intermediate Picture."); + return false; + } + + if (reg_clip && tmp_picture) + XFixesSetPictureClipRegion(ps->dpy, tmp_picture, reg_clip, 0, 0); + + Picture src_pict = tgt_buffer, dst_pict = tmp_picture; + + XRenderComposite(ps->dpy, PictOpHSLLuminosity, src_pict, None, + dst_pict, x, y, 0, 0, 0, 0, wid, hei); + + XserverRegion tmp = src_pict; + src_pict = dst_pict; + dst_pict = tmp; + + if (src_pict != tgt_buffer) + XRenderComposite(ps->dpy, PictOpSrc, src_pict, None, tgt_buffer, + 0, 0, 0, 0, x, y, wid, hei); + + free_picture(ps, &tmp_picture); + + return true; +} + /* * WORK-IN-PROGRESS! static void @@ -1647,6 +1696,35 @@ win_blur_background(session_t *ps, win *w, Picture tgt_buffer, } } +/** + * Set the background of a window to greyscale. + */ +static inline void +win_greyscale_background(session_t *ps, win *w, Picture tgt_buffer, + XserverRegion reg_paint, const reg_data_t *pcache_reg) { + const int x = w->a.x; + const int y = w->a.y; + const int wid = w->widthb; + const int hei = w->heightb; + + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: + { + xr_greyscale_dst(ps, tgt_buffer, x, y, wid, hei, reg_paint); + } + break; +#ifdef CONFIG_VSYNC_OPENGL_GLSL + case BKEND_GLX: + glx_greyscale_dst(ps, x, y, wid, hei, ps->psglx->z - 0.5, + reg_paint, pcache_reg, &w->glx_greyscale_cache); + break; +#endif + default: + assert(0); + } +} + static void render_(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, double opacity, bool argb, bool neg, @@ -2111,6 +2189,11 @@ paint_all(session_t *ps, XserverRegion region, XserverRegion region_real, win *t win_blur_background(ps, w, ps->tgt_buffer.pict, reg_paint, &cache_reg); } + // Set window background to greyscale + if (w->greyscale_background && (!win_is_solid(ps, w))) { + win_greyscale_background(ps, w, ps->tgt_buffer.pict, reg_paint, &cache_reg); + } + // Painting the window win_paint_win(ps, w, reg_paint, &cache_reg); } @@ -2403,6 +2486,7 @@ map_win(session_t *ps, Window id) { } win_determine_blur_background(ps, w); + win_determine_greyscale_background(ps, w); w->damaged = false; @@ -2520,6 +2604,28 @@ get_opacity_percent(win *w) { } static Bool +get_window_transparency_filter_greyscale(const session_t *ps, Window w) +{ + Atom actual; + int format; + unsigned long n, left; + + unsigned char *data; + int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparency_filter_greyscale, 0L, 1L, False, + XA_ATOM, &actual, &format, + &n, &left, &data); + + if (result == Success && data != None && format == 32 ) + { + Atom a; + a = *(long*)data; + XFree ( (void *) data); + return True; + } + return False; +} + +static Bool get_window_transparent_to_desktop(const session_t *ps, Window w) { Atom actual; @@ -2950,6 +3056,32 @@ win_determine_blur_background(session_t *ps, win *w) { win_set_blur_background(ps, w, blur_background_new); } +static void +win_set_greyscale_background(session_t *ps, win *w, bool greyscale_background_new) { + if (w->greyscale_background == greyscale_background_new) return; + + w->greyscale_background = greyscale_background_new; + + // Only consider window damaged if it's previously painted with background + // set to greyscale + if (!win_is_solid(ps, w)) + add_damage_win(ps, w); +} + +/** + * Determine if a window should have a greyscale background. + */ +static void +win_determine_greyscale_background(session_t *ps, win *w) { + if (IsViewable != w->a.map_state) + return; + + bool greyscale_background_new = (get_window_transparency_filter_greyscale(ps, w) || + (ps->o.greyscale_background && !win_match(ps, w, ps->o.greyscale_background_blacklist, &w->cache_bbblst))); + + win_set_greyscale_background(ps, w, greyscale_background_new); +} + /** * Update window opacity according to opacity rules. */ @@ -3006,6 +3138,8 @@ win_on_factor_change(session_t *ps, win *w) { win_update_focused(ps, w); if (ps->o.blur_background_blacklist) win_determine_blur_background(ps, w); + if (ps->o.greyscale_background_blacklist) + win_determine_greyscale_background(ps, w); if (ps->o.opacity_rules) win_update_opacity_rule(ps, w); if (IsViewable == w->a.map_state && ps->o.paint_blacklist) @@ -3273,6 +3407,7 @@ add_win(session_t *ps, Window id, Window prev) { .invert_color_force = UNSET, .blur_background = false, + .greyscale_background = false, .show_black_background = false, .show_root_tile = false, @@ -5037,6 +5172,11 @@ usage(int ret) { " 11x11gaussian.\n" "--blur-background-exclude condition\n" " Exclude conditions for background blur.\n" + "--greyscale-background\n" + " Set background of semi-transparent / ARGB windows to greyscale.\n" + " The switch name may change without prior notifications.\n" + "--greyscale-background-exclude condition\n" + " Exclude conditions for greyscale background.\n" "--resize-damage integer\n" " Resize damaged region by a specific number of pixels. A positive\n" " value enlarges it while a negative one shrinks it. Useful for\n" @@ -5909,12 +6049,16 @@ parse_config(session_t *ps, struct options_tmp *pcfgtmp) { parse_cfg_condlst(ps, &cfg, &ps->o.invert_color_list, "invert-color-include"); // --blur-background-exclude parse_cfg_condlst(ps, &cfg, &ps->o.blur_background_blacklist, "blur-background-exclude"); + // --greyscale-background-exclude + parse_cfg_condlst(ps, &cfg, &ps->o.greyscale_background_blacklist, "greyscale-background-exclude"); // --opacity-rule parse_cfg_condlst_opct(ps, &cfg, "opacity-rule"); // --unredir-if-possible-exclude parse_cfg_condlst(ps, &cfg, &ps->o.unredir_if_possible_blacklist, "unredir-if-possible-exclude"); // --blur-background lcfg_lookup_bool(&cfg, "blur-background", &ps->o.blur_background); + // --greyscale-background + lcfg_lookup_bool(&cfg, "greyscale-background", &ps->o.greyscale_background); // --blur-background-frame lcfg_lookup_bool(&cfg, "blur-background-frame", &ps->o.blur_background_frame); @@ -6066,6 +6210,8 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { { "no-fading-opacitychange", no_argument, NULL, 321 }, { "reredir-on-root-change", no_argument, NULL, 731 }, { "glx-reinit-on-root-change", no_argument, NULL, 732 }, + { "greyscale-background", no_argument, NULL, 733 }, + { "greyscale-background-exclude", required_argument, NULL, 734 }, // Must terminate with a NULL entry { NULL, 0, NULL, 0 }, }; @@ -6339,6 +6485,11 @@ get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) { P_CASEBOOL(321, no_fading_opacitychange); P_CASEBOOL(731, reredir_on_root_change); P_CASEBOOL(732, glx_reinit_on_root_change); + P_CASEBOOL(733, greyscale_background); + case 734: + // --greyscale-background-exclude + condlst_add(ps, &ps->o.greyscale_background_blacklist, optarg); + break; default: usage(1); break; @@ -6479,6 +6630,7 @@ init_atoms(session_t *ps) { ps->atom_win_type_tde_transparent_to_black = get_atom(ps, "_TDE_TRANSPARENT_TO_BLACK"); ps->atom_win_type_tde_transparent_to_desktop = get_atom(ps, "_TDE_TRANSPARENT_TO_DESKTOP"); + ps->atom_win_type_tde_transparency_filter_greyscale = get_atom(ps, "_TDE_TRANSPARENCY_FILTER_GREYSCALE"); } #ifdef CONFIG_XRANDR @@ -7350,6 +7502,7 @@ session_init(session_t *ps_old, int argc, char **argv) { .blur_background_fixed = false, .blur_background_blacklist = NULL, .blur_kerns = { NULL }, + .greyscale_background = false, .inactive_dim = 0.0, .inactive_dim_fixed = false, .invert_color_list = NULL, @@ -7447,6 +7600,7 @@ session_init(session_t *ps_old, int argc, char **argv) { .atom_win_type = None, .atom_win_type_tde_transparent_to_black = None, .atom_win_type_tde_transparent_to_desktop = None, + .atom_win_type_tde_transparency_filter_greyscale = None, .atoms_wintypes = { 0 }, .track_atom_lst = NULL, @@ -7851,6 +8005,7 @@ session_destroy(session_t *ps) { free_wincondlst(&ps->o.focus_blacklist); free_wincondlst(&ps->o.invert_color_list); free_wincondlst(&ps->o.blur_background_blacklist); + free_wincondlst(&ps->o.greyscale_background_blacklist); free_wincondlst(&ps->o.opacity_rules); free_wincondlst(&ps->o.paint_blacklist); free_wincondlst(&ps->o.unredir_if_possible_blacklist); diff --git a/twin/compton-tde/compton.h b/twin/compton-tde/compton.h index c1c877056..b91081398 100644 --- a/twin/compton-tde/compton.h +++ b/twin/compton-tde/compton.h @@ -871,6 +871,12 @@ static void win_determine_blur_background(session_t *ps, win *w); static void +win_set_greyscale_background(session_t *ps, win *w, bool greyscale_background_new); + +static void +win_determine_greyscale_background(session_t *ps, win *w); + +static void win_on_wtype_change(session_t *ps, win *w); static void diff --git a/twin/compton-tde/opengl.c b/twin/compton-tde/opengl.c index 2b9ff6286..de485de74 100644 --- a/twin/compton-tde/opengl.c +++ b/twin/compton-tde/opengl.c @@ -1386,6 +1386,175 @@ glx_blur_dst_end: #endif bool +glx_greyscale_dst(session_t *ps, int dx, int dy, int width, int height, float z, + XserverRegion reg_tgt, const reg_data_t *pcache_reg, glx_greyscale_cache_t *pbc) { + bool ret = false; + + // Calculate copy region size + glx_greyscale_cache_t ibc = { .width = 0, .height = 0 }; + if (!pbc) + pbc = &ibc; + +#ifdef DEBUG_GLX + printf_dbgf("(): %d, %d, %d, %d\n", dx, dy, width, height); +#endif + + // Free textures if size inconsistency discovered + if (width != pbc->width || height != pbc->height) + free_glx_gc_resize(ps, pbc); + + // Generate FBO and textures if needed + if (!pbc->textures[0]) + pbc->textures[0] = glx_gen_texture(ps, GL_TEXTURE_2D, width, height); + GLuint tex_scr1 = pbc->textures[0]; + pbc->width = width; + pbc->height = height; + + if (!tex_scr1) { + printf_errf("(): Failed to allocate texture."); + goto glx_greyscale_dst_end; + } + + // Texture scaling factor + GLfloat texfac_x = 1.0f, texfac_y = 1.0f; + texfac_x /= width; + texfac_y /= height; + + // Greyscale conversion in OpenGL ES taken nearly verbatim from this answer on Stack Overflow: http://stackoverflow.com/a/9690145 + + // Enable texture unit 0 to divide RGB values in our texture by 2 + glActiveTexture(GL_TEXTURE0); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tex_scr1); + + // Read destination pixels into the GL texture + glx_copy_region_to_tex(ps, GL_TEXTURE_2D, dx, dy, dx, dy, width, height); + + // Finish setting up texture + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glClientActiveTexture(GL_TEXTURE0); + + // GL_MODULATE is Arg0 * Arg1 + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); + + // Configure Arg0 + glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); + + // Configure Arg1 + float multipliers[4] = {.5, .5, .5, 0.0}; + glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat*)&multipliers); + + // Enable texture unit 1 to increase RGB values by .5 + glActiveTexture(GL_TEXTURE1); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tex_scr1); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glClientActiveTexture(GL_TEXTURE1); + + // GL_ADD is Arg0 + Arg1 + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD); + + // Configure Arg0 + glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); + + // Configure Arg1 + GLfloat additions[4] = {.5, .5, .5, 0.0}; + glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat*)&additions); + + // Enable texture combiner 2 to get a DOT3_RGB product of your RGB values + glActiveTexture(GL_TEXTURE2); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tex_scr1); + glClientActiveTexture(GL_TEXTURE2); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + + // GL_DOT3_RGB is 4*((Arg0r - 0.5) * (Arg1r - 0.5) + (Arg0g - 0.5) * (Arg1g - 0.5) + (Arg0b - 0.5) * (Arg1b - 0.5)) + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB); + + // Configure Arg0 + glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); + + // Configure Arg1 + // We want this to adjust our DOT3 by R*0.3 + G*0.59 + B*0.11 + // So, our actual adjustment will need to take into consideration + // the fact that OpenGL will subtract .5 from our Arg1 + // and we need to also take into consideration that we have divided + // our RGB values by 2 and we are multiplying the entire + // DOT3 product by 4 + // So, for Red adjustment you will get : + // .65 = (4*(0.3))/2 + 0.5 = (0.3/2) + 0.5 + GLfloat weights[4] = {.65, .795, .555, 1.}; + glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat*)&weights); + + // Render! + { + P_PAINTREG_START(); + { + const GLfloat rx = (crect.x - dx) * texfac_x; + const GLfloat ry = (height - (crect.y - dy)) * texfac_y; + const GLfloat rxe = rx + crect.width * texfac_x; + const GLfloat rye = ry - crect.height * texfac_y; + GLfloat rdx = crect.x; + GLfloat rdy = ps->root_height - crect.y; + GLfloat rdxe = rdx + crect.width; + GLfloat rdye = rdy - crect.height; + +#ifdef DEBUG_GLX + printf_dbgf("(): %f, %f, %f, %f -> %f, %f, %f, %f\n", rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); +#endif + + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); + } + P_PAINTREG_END(); + } + + glEnd(); + + // Clean up by disabling your texture combiners or texture units. + glActiveTexture(GL_TEXTURE2); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glDisable(GL_TEXTURE_2D); + + glActiveTexture(GL_TEXTURE1); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glDisable(GL_TEXTURE_2D); + + glActiveTexture(GL_TEXTURE0); + glClientActiveTexture(GL_TEXTURE0); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + ret = true; + +glx_greyscale_dst_end: + if (&ibc == pbc) { + free_glx_gc(ps, pbc); + } + + glx_check_err(ps); + + return ret; +} + +bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, float z, GLfloat factor, XserverRegion reg_tgt, const reg_data_t *pcache_reg) { // It's possible to dim in glx_render(), but it would be over-complicated |