/** * Beryl Opacify * * Copyright (c) 2006 Kristian Lyngstøl * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * Opacify increases opacity on targeted windows and reduces it on * blocking windows, making whatever window you are targeting easily * visible. * */ #include #include #include #include #include #include #include #define GET_OPACIFY_DISPLAY(d) \ ((OpacifyDisplay *) (d)->privates[displayPrivateIndex].ptr) #define OPACIFY_DISPLAY(d) \ OpacifyDisplay *od = GET_OPACIFY_DISPLAY (d) #define GET_OPACIFY_SCREEN(s, od) \ ((OpacifyScreen *) (s)->privates[(od)->screenPrivateIndex].ptr) #define OPACIFY_SCREEN(s) \ OpacifyScreen *os = GET_OPACIFY_SCREEN (s, GET_OPACIFY_DISPLAY (s->display)) #define OPACIFY_DISPLAY_OPTION_TOGGLE 0 #define OPACIFY_DISPLAY_OPTION_TOGGLE_FREEZE 1 #define OPACIFY_DISPLAY_OPTION_INIT_TOGGLE 2 #define OPACIFY_DISPLAY_OPTION_NUM 3 #define OPACIFY_TOGGLE_KEY "o" #define OPACIFY_TOGGLE_MOD CompSuperMask #define OPACIFY_SCREEN_OPTION_ACTIVE_OP 0 #define OPACIFY_SCREEN_OPTION_PASSIVE_OP 1 #define OPACIFY_SCREEN_OPTION_ONLY_IF_BLOCK 2 #define OPACIFY_SCREEN_OPTION_WINDOW_TYPE 3 #define OPACIFY_SCREEN_OPTION_TIMEOUT 4 #define OPACIFY_SCREEN_OPTION_FOCUS_INSTANT 5 #define OPACIFY_SCREEN_OPTION_NO_DELAY_CHANGE 6 #define OPACIFY_SCREEN_OPTION_NUM 7 #define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption)) #define N_WIN_TYPE (sizeof (winType) / sizeof (winType[0])) /* Size of the Window array storing passive windows. */ #define MAX_WINDOWS 64 static int displayPrivateIndex = 0; typedef struct _OpacifyDisplay { int screenPrivateIndex; HandleEventProc handleEvent; Bool toggle; Bool toggle_reset; CompOption opt[OPACIFY_DISPLAY_OPTION_NUM]; } OpacifyDisplay; static char *winType[] = { N_("Toolbar"), N_("Utility"), N_("Dialog"), N_("ModalDialog"), N_("Fullscreen"), N_("Normal") }; typedef struct _OpacifyScreen { CompWindow *new_active; Window active; Window passive[MAX_WINDOWS]; Region intersect; unsigned short int passive_num; unsigned int active_op; unsigned int passive_op; unsigned int timeout; int wMask; Bool only_if_block; Bool just_moved; CompTimeoutHandle timeout_handle; CompOption opt[OPACIFY_SCREEN_OPTION_NUM]; } OpacifyScreen; /* Core opacify functions. These do the real work. ---------------------*/ /* Finds the corrosponding CompWindow for Window id. Returns NULL if * there is no such window. */ static CompWindow *find_window(CompScreen * s, Window id) { CompWindow *w; for (w = s->windows; w; w = w->next) if (w->id == id) return w; return NULL; } /* Sets the real opacity and damages the window if actual opacity and * requested opacity differs. */ static void set_opacity(CompWindow * w, int opacity) { setWindowOpacity(w, opacity, PL_TEMP_HELLO); } /* Resets the Window to the original opacity if it still exists. */ static void reset_opacity(CompScreen * s, Window ow) { CompWindow *w; if (!ow) return; w = find_window(s, ow); resetWindowOpacity(w, PL_TEMP_HELLO); } /* Resets the opacity of windows on the passive list. */ static void clear_passive(CompScreen * s) { OPACIFY_SCREEN(s); int i; for (i = 0; i < os->passive_num; i++) reset_opacity(s, os->passive[i]); os->passive_num = 0; } /* Dim an (inactive) window. Place it on the passive list and * update passive_num. Then change the opacity. */ static void dim_window(CompScreen * s, CompWindow * w) { OPACIFY_SCREEN(s); if (UNLIKELY(os->passive_num >= MAX_WINDOWS - 1)) { fprintf(stderr, "opacify: Trying to store information " "about too many windows, or you hit a bug.\nIf " "you don't have around %d windows blocking the " "currently targeted window, please report this.\n", MAX_WINDOWS); return; } os->passive[os->passive_num++] = w->id; set_opacity(w, MIN(os->passive_op, w->paint.opacity)); } /* Walk through all windows, skip until we've passed the active * window, skip if it's invisible, hidden or minimized, skip if * it's not a window type we're looking for. * Dim it if it intersects. * * Returns number of changed windows. */ static int passive_windows(CompScreen * s, Region a_region) { CompWindow *w; OPACIFY_SCREEN(s); Bool flag = FALSE; int i = 0; for (w = s->windows; w; w = w->next) { if (w->id == os->active) { flag = TRUE; continue; } if (!flag) continue; if (!(w->type & os->wMask)) continue; if (w->invisible || w->hidden || w->minimized) continue; XIntersectRegion(w->region, a_region, os->intersect); if (!XEmptyRegion(os->intersect)) { dim_window(s, w); i++; } } return i; } /* Check if we switched active window, reset the old passive windows * if we did. If we have an active window and switched: reset that too. * If we have a window (w is true), update the active id and * passive list. just_moved is to make sure we recalculate opacity after * moving. We can't reset before moving because if we're using a delay * and the window being moved is not the active but overlapping, it will * be reset, which would conflict with move's opacity change. * FIXME: A more final solution should be to use IPCS to signal * which window is being moved. */ static void opacify_handle_enter(CompScreen * s, CompWindow * w) { OPACIFY_SCREEN(s); int num; if (screenGrabExist(s, "move", 0)) { os->just_moved = True; return; } if (screenGrabExist(s, "rotate", "scale", "resize", 0)) { clear_passive(s); reset_opacity(s, os->active); os->active = 0; return; } if (!w || os->active != w->id || os->just_moved) { os->just_moved = False; clear_passive(s); reset_opacity(s, os->active); os->active = 0; } if (!w) return; if (w->id != os->active && (w->type & os->wMask) && !w->shaded) { os->active = w->id; num = passive_windows(s, w->region); if (num || !os->only_if_block) set_opacity(w, MAX(os->active_op, w->paint.opacity)); } } /* Decides what to do after a timeout occured. * Either we reset the opacity because we just toggled, * or we handle the event. */ static Bool handle_timeout(void *data) { CompScreen *s = (CompScreen *) data; OPACIFY_SCREEN(s); OPACIFY_DISPLAY(s->display); os->timeout_handle = 0; if (UNLIKELY(!od->toggle)) { clear_passive(s); reset_opacity(s, os->active); os->active = 0; } opacify_handle_enter(s, os->new_active); return FALSE; } /* Checks whether we should delay or not. * Returns true if immediate execution. */ static inline Bool check_delay(OpacifyScreen *os, CompScreen *s, CompDisplay *d) { if ((os->opt[OPACIFY_SCREEN_OPTION_FOCUS_INSTANT].value.b && os->new_active && (os->new_active->id == d->activeWindow))) return True; if (!os->timeout) return True; if (!os->new_active || (os->new_active->id == s->root)) return False; if (os->new_active->type & (CompWindowTypeDesktopMask | CompWindowTypeDockMask)) return False; if (os->opt[OPACIFY_SCREEN_OPTION_NO_DELAY_CHANGE].value.b && os->passive_num) return True; return False; } /* Takes the inital event. * If we were configured, recalculate the opacify-windows if * it was our window. * If a window was entered: call upon handle_timeout after os->timeout * micro seconds, or directly if os->timeout is 0 (no delay). * * FIXME: In the perfect world, toggle-resetting is done in the action * handler that does the actual toggling. */ static void opacifyHandleEvent(CompDisplay * d, XEvent * event) { CompScreen *s; CompWindow *w = NULL; OPACIFY_DISPLAY(d); UNWRAP(od, d, handleEvent); (*d->handleEvent) (d, event); WRAP(od, d, handleEvent, opacifyHandleEvent); if (!od->toggle && !od->toggle_reset) return; switch (event->type) { case EnterNotify: s = findScreenAtDisplay(d, event->xcrossing.root); if (LIKELY(s)) { OPACIFY_SCREEN(s); if (!od->toggle && !os->active) return; os->new_active = findTopLevelWindowAtScreen(s, event->xcrossing.window); if (os->timeout_handle) compRemoveTimeout(os->timeout_handle); if (check_delay(os,s,d)) handle_timeout(s); else os->timeout_handle = compAddTimeout(os->timeout, handle_timeout, s); } break; case ConfigureNotify: s = findScreenAtDisplay(d, event->xconfigure.event); if (LIKELY(s)) { OPACIFY_SCREEN(s); if (os->active != event->xconfigure.window) break; clear_passive(s); if (LIKELY(os->active)) w = find_window(s, os->active); if (w) passive_windows(s, w->region); } break; default: break; } } /* Configuration, initialization, boring stuff. ----------------------- */ static void opacifyFiniScreen(CompPlugin * p, CompScreen * s) { OPACIFY_SCREEN(s); OPACIFY_DISPLAY(s->display); if (os->opt[OPACIFY_SCREEN_OPTION_WINDOW_TYPE].value.list.value) free(os->opt[OPACIFY_SCREEN_OPTION_WINDOW_TYPE].value.list.value); removeScreenAction(s, &od->opt[OPACIFY_DISPLAY_OPTION_TOGGLE].value. action); if (os->timeout_handle) compRemoveTimeout(os->timeout_handle); XDestroyRegion(os->intersect); free(os); } static Bool opacifySetScreenOptions(CompScreen * screen, char *name, CompOptionValue * value) { CompOption *o; int index; OPACIFY_SCREEN(screen); o = compFindOption(os->opt, NUM_OPTIONS(os), name, &index); if (!o) return FALSE; switch (index) { case OPACIFY_SCREEN_OPTION_ACTIVE_OP: if (compSetIntOption(o, value)) { os->active_op = (o->value.i * 0xffff) / 100; return TRUE; } break; case OPACIFY_SCREEN_OPTION_PASSIVE_OP: if (compSetIntOption(o, value)) { os->passive_op = (o->value.i * 0xffff) / 100; return TRUE; } break; case OPACIFY_SCREEN_OPTION_TIMEOUT: if (compSetIntOption(o, value)) { os->timeout = o->value.i * 100; return TRUE; } break; case OPACIFY_SCREEN_OPTION_WINDOW_TYPE: if (compSetOptionList(o, value)) { os->wMask = compWindowTypeMaskFromStringList(&o->value); return TRUE; } break; case OPACIFY_SCREEN_OPTION_FOCUS_INSTANT: case OPACIFY_SCREEN_OPTION_NO_DELAY_CHANGE: if (compSetBoolOption(o, value)) return TRUE; break; case OPACIFY_SCREEN_OPTION_ONLY_IF_BLOCK: if (compSetBoolOption(o, value)) { os->only_if_block = o->value.b; return TRUE; } break; default: break; } return FALSE; } static void opacifyScreenInitOptions(OpacifyScreen * os) { CompOption *o; int i; o = &os->opt[OPACIFY_SCREEN_OPTION_ONLY_IF_BLOCK]; o->name = "only_if_block"; o->group = N_("Misc. options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Only increase opacity if a window is blocking"); o->longDesc = N_ ("Only increase the opacity on the targeted window if it " "has one or more windows blocking it from view."); o->type = CompOptionTypeBool; o->value.b = FALSE; o->advanced = False; os->only_if_block = FALSE; o = &os->opt[OPACIFY_SCREEN_OPTION_FOCUS_INSTANT]; o->name = "focus_instant"; o->group = N_("Misc. options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_ ("Bypass delay when the new active window is the focused window."); o->longDesc = N_("Do not wait if the window we are hovering is the focused " "window. This allows us to instantly see the focused window." "You probably want to disable this if you are not using " "\"Click to Focus\"."); o->type = CompOptionTypeBool; o->value.b = FALSE; o->advanced = True; o = &os->opt[OPACIFY_SCREEN_OPTION_NO_DELAY_CHANGE]; o->name = "no_delay_change"; o->group = N_("Misc. options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_ ("Bypass delay when Opacify is reducing the opacity on one or more windows already."); o->longDesc = N_("This enables you to let Opacify instantly opacify new " "windows when you're already making one or more windows invisible. " "Makes for faster behavior while looking through layers of hidden windows."); o->type = CompOptionTypeBool; o->value.b = FALSE; o->advanced = True; o = &os->opt[OPACIFY_SCREEN_OPTION_ACTIVE_OP]; o->name = "active_op"; o->group = N_("Misc. options"); o->subGroup = N_("Opacity levels"); o->displayHints = ""; o->shortDesc = N_("Active Opacity"); o->longDesc = N_ ("The minimum opacity to ensure a targeted window has. " "A target window will have either this opacity or the " "preset opacity, whichever is higher."); o->type = CompOptionTypeInt; o->value.i = 100; o->rest.i.min = 1; o->rest.i.max = 100; o->advanced = False; os->active_op = OPAQUE; o = &os->opt[OPACIFY_SCREEN_OPTION_PASSIVE_OP]; o->name = "passive_op"; o->group = N_("Misc. options"); o->subGroup = N_("Opacity levels"); o->displayHints = ""; o->shortDesc = N_("Passive Opacity"); o->longDesc = N_ ("The maximum opacity a window blocking the current targeted " "window can have. A blocking window will have either this " "opacity or the preset opacity, whichever is lower."); o->type = CompOptionTypeInt; o->value.i = 10; o->rest.i.min = 1; o->rest.i.max = 100; o->advanced = False; os->passive_op = 0xffff / 10; o = &os->opt[OPACIFY_SCREEN_OPTION_TIMEOUT]; o->name = "timeout"; o->group = N_("Misc. options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Delay until Opacification"); o->longDesc = N_ ("The delay in 1/10th of a second before Opacify changes " "opacity after the active window has changed."); o->type = CompOptionTypeInt; o->value.i = 7; o->rest.i.min = 0; o->rest.i.max = 100; o->advanced = False; os->timeout = 700; o = &os->opt[OPACIFY_SCREEN_OPTION_WINDOW_TYPE]; o->name = "window_types"; o->group = N_("Misc. options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Window Types"); o->longDesc = N_("Window types that should be Opacified."); o->type = CompOptionTypeList; o->value.list.type = CompOptionTypeString; o->value.list.nValue = N_WIN_TYPE; o->value.list.value = malloc(sizeof(CompOptionValue) * N_WIN_TYPE); for (i = 0; i < N_WIN_TYPE; i++) o->value.list.value[i].s = strdup(winType[i]); o->rest.s.string = (char **)windowTypeString; o->rest.s.nString = nWindowTypeString; o->advanced = True; os->wMask = compWindowTypeMaskFromStringList(&o->value); } static Bool opacifyInitScreen(CompPlugin * p, CompScreen * s) { OPACIFY_DISPLAY(s->display); OpacifyScreen *os = (OpacifyScreen *) calloc(1, sizeof(OpacifyScreen)); s->privates[od->screenPrivateIndex].ptr = os; os->intersect = XCreateRegion(); opacifyScreenInitOptions(os); os->just_moved = False; addScreenAction(s, &od->opt[OPACIFY_DISPLAY_OPTION_TOGGLE].value.action); return TRUE; } static CompOption *opacifyGetScreenOptions(CompScreen * screen, int *count) { if (screen) { OPACIFY_SCREEN(screen); *count = NUM_OPTIONS(os); return os->opt; } else { OpacifyScreen *os = malloc(sizeof(OpacifyScreen)); opacifyScreenInitOptions(os); *count = NUM_OPTIONS(os); return os->opt; } } static Bool opacify_toggle(CompDisplay * d, CompAction * ac, CompActionState state, CompOption * option, int nOption) { OPACIFY_DISPLAY(d); od->toggle = !od->toggle; return TRUE; } static void opacifyDisplayInitOptions(OpacifyDisplay * od) { CompOption *o; o = &od->opt[OPACIFY_DISPLAY_OPTION_TOGGLE]; o->name = "toggle"; o->group = N_("Bindings"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Toggle Opacify"); o->longDesc = N_ ("Use this to enable/disable Opacify on the fly. Previously " "opacified windows will not be reset once you disable it like this."); o->type = CompOptionTypeAction; o->value.action.initiate = opacify_toggle; o->value.action.terminate = 0; o->value.action.bell = FALSE; o->value.action.edgeMask = 0; o->value.action.type = CompBindingTypeKey; o->value.action.state = CompActionStateInitKey; o->value.action.key.modifiers = OPACIFY_TOGGLE_MOD; o->value.action.key.keysym = XStringToKeysym(OPACIFY_TOGGLE_KEY); o->advanced = False; o = &od->opt[OPACIFY_DISPLAY_OPTION_TOGGLE_FREEZE]; o->name = "toggle_reset"; o->group = N_("Misc. options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Reset opacity to original values when toggling"); o->longDesc = N_ ("Reset the opacity of all windows modified by opacify when " "toggling Opacify with the defined key-combination."); o->type = CompOptionTypeBool; o->value.b = TRUE; o->advanced = True; od->toggle_reset = TRUE; o = &od->opt[OPACIFY_DISPLAY_OPTION_INIT_TOGGLE]; o->name = "init_toggle"; o->group = N_("Misc. options"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Toggle opacify on by default"); o->longDesc = N_ ("With this enabled, opacify will be on when you load Opacify, " "which is usually when you start Beryl."); o->type = CompOptionTypeBool; o->value.b = True; o->advanced = False; od->toggle = True; } static Bool opacifySetDisplayOptions(CompDisplay * display, char *name, CompOptionValue * value) { CompOption *o; int index; OPACIFY_DISPLAY(display); o = compFindOption(od->opt, NUM_OPTIONS(od), name, &index); if (!o) return FALSE; switch (index) { case OPACIFY_DISPLAY_OPTION_TOGGLE: if (setDisplayAction(display, o, value)) return TRUE; break; case OPACIFY_DISPLAY_OPTION_INIT_TOGGLE: if (compSetBoolOption(o, value)) { od->toggle = o->value.b; return TRUE; } break; case OPACIFY_DISPLAY_OPTION_TOGGLE_FREEZE: if (compSetBoolOption(o, value)) { od->toggle_reset = o->value.b; return TRUE; } break; default: break; } return FALSE; } static CompOption *opacifyGetDisplayOptions(CompDisplay * display, int *count) { if (display) { OPACIFY_DISPLAY(display); *count = NUM_OPTIONS(od); return od->opt; } else { OpacifyDisplay *od = malloc(sizeof(OpacifyDisplay)); opacifyDisplayInitOptions(od); *count = NUM_OPTIONS(od); return od->opt; } } static Bool opacifyInitDisplay(CompPlugin * p, CompDisplay * d) { OpacifyDisplay *od = (OpacifyDisplay *) malloc(sizeof(OpacifyDisplay)); od->screenPrivateIndex = allocateScreenPrivateIndex(d); if (od->screenPrivateIndex < 0) { free(od); return FALSE; } opacifyDisplayInitOptions(od); d->privates[displayPrivateIndex].ptr = od; WRAP(od, d, handleEvent, opacifyHandleEvent); return TRUE; } static void opacifyFiniDisplay(CompPlugin * p, CompDisplay * d) { OPACIFY_DISPLAY(d); UNWRAP(od, d, handleEvent); freeScreenPrivateIndex(d, od->screenPrivateIndex); free(od); } static Bool opacifyInit(CompPlugin * p) { displayPrivateIndex = allocateDisplayPrivateIndex(); if (displayPrivateIndex < 0) return FALSE; return TRUE; } static void opacifyFini(CompPlugin * p) { if (displayPrivateIndex >= 0) freeDisplayPrivateIndex(displayPrivateIndex); } CompPluginVTable opacifyVTable = { "opacify", N_("Opacify"), N_("Make windows easily visible by hovering the mouse over them"), opacifyInit, opacifyFini, opacifyInitDisplay, opacifyFiniDisplay, opacifyInitScreen, opacifyFiniScreen, 0, 0, opacifyGetDisplayOptions, opacifySetDisplayOptions, opacifyGetScreenOptions, opacifySetScreenOptions, 0, 0, 0, 0, BERYL_ABI_INFO, "beryl-plugins", "accessibility", 0, 0, False, }; CompPluginVTable *getCompPluginInfo(void) { return &opacifyVTable; }