/* * Beryl Snap Plugin * Author : Guillaume "iXce" Seguin * Email : ixce@beryl-project.org * * Copyright (C) 2007 Guillaume Seguin * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * TODO * - Apply Edge Resistance to resize */ #include #include #include #include /* * The neat window coordinates macros */ #define WIN_X(w) ((w)->attrib.x - (w)->input.left) #define WIN_Y(w) ((w)->attrib.y - (w)->input.top) #define WIN_W(w) ((w)->width + (w)->input.left + (w)->input.right) #define WIN_H(w) ((w)->height + (w)->input.top + (w)->input.bottom) /* * The window we should snap too if snapping to windows */ #define SNAP_WINDOW_TYPE (CompWindowTypeNormalMask | \ CompWindowTypeToolbarMask | \ CompWindowTypeMenuMask | \ CompWindowTypeUtilMask) /* * Options stuff */ #define SNAP_ATTRACTION_DISTANCE_DEFAULT 20 #define SNAP_RESISTANCE_DISTANCE_DEFAULT 30 #define SNAP_EDGE_DISTANCE_MIN 1 #define SNAP_EDGE_DISTANCE_MAX 100 typedef enum _SnapType { SnapTypeResistance, SnapTypeFull } SnapType; char *snapTypes[] = { N_("Edge resistance"), N_("Edge resistance & attraction") }; #define SNAP_TYPE_OPTION_DEFAULT SnapTypeResistance #define NUM_SNAP_TYPES 2 typedef enum _EdgesCategory { EdgesCategoryScreen, EdgesCategoryWindows, EdgesCategoryBoth } EdgesCategory; char *edgesCategories[] = { N_("Screen edges"), N_("Windows edges"), N_("Screen & windows edges") }; #define EDGES_CATEGORY_OPTION_DEFAULT EdgesCategoryBoth #define NUM_EDGES_CATEGORIES 3 #define nMods 4 static char *Mods[] = { N_("Shift"), N_("Alt"), N_("Control"), N_("Meta") }; static int ModMask[] = { ShiftMask, CompAltMask, ControlMask, CompMetaMask, }; #define AVOID_SNAP_MODIFIERS_DEFAULT ShiftMask /* * Masks * *Snap : snap directions * Edge* : snap modes * *Edges : edges categories we snap to */ #define VerticalSnap (1L << 0) #define HorizontalSnap (1L << 1) #define EdgeResistance (1L << 0) #define EdgeAttraction (1L << 1) #define SNAP_TYPE_DEFAULT EdgeResistance #define ScreenEdges (1L << 0) #define WindowsEdges (1L << 1) #define EDGES_CATEGORIES_DEFAULT (ScreenEdges | WindowsEdges) #define MoveGrab (1L << 0) #define ResizeGrab (1L << 1) typedef enum { LeftEdge = 0, RightEdge, TopEdge, BottomEdge } EdgeType; typedef struct _Edge Edge; /* Custom Edge struct, linked list * Position, start, end meanings are specific to type : * - LeftEdge/RightEdge : position : x, start/end : y1/y2 * - TopEdge/BottomEdge : position : y, start/end : x1/x2 * id/passed are used during visibility detection when adding edges * snapped is straight forward */ struct _Edge { Edge *prev; Edge *next; int position; int start; int end; EdgeType type; Bool screenEdge; Window id; Bool passed; Bool snapped; }; static int displayPrivateIndex; #define SNAP_DISPLAY_OPTION_AVOID_SNAP 0 #define SNAP_DISPLAY_OPTION_NUM 1 typedef struct _SnapDisplay { int screenPrivateIndex; HandleEventProc handleEvent; CompOption opt[SNAP_DISPLAY_OPTION_NUM]; int avoidSnapMask; // Used to check is avoidSnapMask is currently matched Bool snapping; } SnapDisplay; #define SNAP_SCREEN_OPTION_SNAP_TYPE 0 #define SNAP_SCREEN_OPTION_EDGES_CATEGORIES 1 #define SNAP_SCREEN_OPTION_RESISTANCE_DISTANCE 2 #define SNAP_SCREEN_OPTION_ATTRACTION_DISTANCE 3 #define SNAP_SCREEN_OPTION_NUM 4 typedef struct _SnapScreen { int windowPrivateIndex; WindowResizeNotifyProc windowResizeNotify; WindowMoveNotifyProc windowMoveNotify; WindowGrabNotifyProc windowGrabNotify; WindowUngrabNotifyProc windowUngrabNotify; CompOption opt[SNAP_SCREEN_OPTION_NUM]; // bitfields int snapType; int edgesCategories; float resistanceDistance; float attractionDistance; } SnapScreen; typedef struct _SnapWindow { // Linked lists Edge *edges; Edge *reverseEdges; // bitfield int snapDirection; // dx/dy/dw/dh when a window is resisting to user int dx; int dy; int dw; int dh; // Internals Bool snapped; int grabbed; // Internal, avoids infinite notify loops Bool skipNotify; } SnapWindow; #define GET_SNAP_DISPLAY(d) \ ((SnapDisplay *) (d)->privates[displayPrivateIndex].ptr) #define SNAP_DISPLAY(d) \ SnapDisplay *sd = GET_SNAP_DISPLAY (d) #define GET_SNAP_SCREEN(s, sd) \ ((SnapScreen *) (s)->privates[(sd)->screenPrivateIndex].ptr) #define SNAP_SCREEN(s) \ SnapScreen *ss = GET_SNAP_SCREEN (s, GET_SNAP_DISPLAY (s->display)) #define GET_SNAP_WINDOW(w, ss) \ ((SnapWindow *) (w)->privates[(ss)->windowPrivateIndex].ptr) #define SNAP_WINDOW(w) \ SnapWindow *sw = GET_SNAP_WINDOW (w, \ GET_SNAP_SCREEN (w->screen, \ GET_SNAP_DISPLAY (w->screen->display))) #define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption)) // Helper functions ------------------------------------------------------------ /* * Wrapper functions to avoid infinite notify loops */ static void snapMoveWindow(CompWindow * w, int dx, int dy) { SNAP_WINDOW(w); sw->skipNotify = TRUE; moveWindow(w, dx, dy, TRUE, TRUE); warpPointer(w->screen->display, dx, dy); sw->skipNotify = FALSE; } static void snapResizeWindow(CompWindow * w, int dx, int dy, int dw, int dh, Bool preview) { SNAP_WINDOW(w); //fprintf (stderr, "Resizing: %d, %d, %d, %d\n", dx, dy, dw, dh); sw->skipNotify = TRUE; if (preview) resizeWindowPreview(w, w->attrib.x + dx, w->attrib.y + dy, w->attrib.width + dw, w->attrib.height + dh); else resizeWindow(w, w->attrib.x + dx, w->attrib.y + dy, w->attrib.width + dw, w->attrib.height + dh, w->attrib.border_width); sw->skipNotify = FALSE; } static void snapFreeEdges(CompWindow * w) { SNAP_WINDOW(w); Edge *current = sw->edges, *next; while (current) { next = current->next; free(current); current = next; } sw->edges = sw->reverseEdges = NULL; } static void snapRemoveEdge(Edge * edge) { if (edge == NULL) return; if (edge->prev) edge->prev->next = edge->next; if (edge->next) edge->next->prev = edge->prev; free(edge); } static Edge *snapAddEdge(Edge ** edges, Edge ** reverseEdges, Window id, int position, int start, int end, EdgeType type, Bool screenEdge) { Edge *edge = malloc(sizeof(Edge)); if (!edge) return NULL; edge->next = NULL; edge->position = position; edge->start = start; edge->end = end; edge->type = type; edge->screenEdge = screenEdge; edge->snapped = FALSE; edge->passed = FALSE; edge->id = id; if (!*edges) { edge->prev = NULL; *reverseEdges = *edges = edge; } else { edge->prev = *reverseEdges; edge->prev->next = edge; *reverseEdges = edge; } return edge; } /* * Add an edge for each rectangle of the region */ static void snapAddRegionEdges(SnapWindow * sw, Edge * parent, Region region) { Edge *edge; int i, position, start, end; for (i = 0; i < region->numRects; i++) { switch (parent->type) { case LeftEdge: case RightEdge: position = region->rects[i].x1; start = region->rects[i].y1; end = region->rects[i].y2; break; case TopEdge: case BottomEdge: default: position = region->rects[i].y1; start = region->rects[i].x1; end = region->rects[i].x2; } edge = snapAddEdge(&sw->edges, &sw->reverseEdges, parent->id, position, start, end, parent->type, parent->screenEdge); if (edge) edge->passed = parent->passed; } } /* Checks if a window is considered a snap window. If it's * not visible, returns false. If it's a panel and we're * snapping to screen edges, it's considered a snap-window. */ static inline Bool isSnapWindow(CompWindow * w) { SNAP_SCREEN(w->screen); if (UNLIKELY(!w)) return FALSE; if (w->invisible || w->hidden || w->minimized) return FALSE; if (w->state & CompWindowStateOffscreenMask) return FALSE; if ((w->type & SNAP_WINDOW_TYPE) && (ss->edgesCategories & WindowsEdges)) return TRUE; if (w->struts && (ss->edgesCategories & ScreenEdges)) return TRUE; return FALSE; } // Edges update functions ------------------------------------------------------ /* * Detect visible windows edges */ static void snapUpdateWindowsEdges(CompWindow * w) { CompWindow *c = NULL; Edge *e = NULL, *next = NULL; SNAP_WINDOW(w); Region edgeRegion, resultRegion; XRectangle rect; Bool remove = FALSE; // First add all the windows c = w->screen->windows; while (c) { // Just check that we're not trying to snap to current window, // that the window is not invisible and of a valid type if (c == w || !isSnapWindow(c)) { c = c->next; continue; } snapAddEdge(&sw->edges, &sw->reverseEdges, c->id, WIN_Y(c), WIN_X(c), WIN_X(c) + WIN_W(c), TopEdge, FALSE); snapAddEdge(&sw->edges, &sw->reverseEdges, c->id, WIN_Y(c) + WIN_H(c), WIN_X(c), WIN_X(c) + WIN_W(c), BottomEdge, FALSE); snapAddEdge(&sw->edges, &sw->reverseEdges, c->id, WIN_X(c), WIN_Y(c), WIN_Y(c) + WIN_H(c), LeftEdge, FALSE); snapAddEdge(&sw->edges, &sw->reverseEdges, c->id, WIN_X(c) + WIN_W(c), WIN_Y(c), WIN_Y(c) + WIN_H(c), RightEdge, FALSE); c = c->next; } // Now strip invisible edges // Loop through all the windows stack, and through all the edges // If an edge has been passed, check if it's in the region window, // if the edge is fully under the window, drop it, or if it's only // partly covered, cut it/split it in one/two smaller visible edges for (c = w->screen->windows; c; c = c->next) { if (c == w || !isSnapWindow(c)) continue; for (e = sw->edges; e; e = next) { if (!e->passed) { if (e->id == c->id) e->passed = TRUE; next = e->next; continue; } switch (e->type) { case LeftEdge: case RightEdge: rect.x = e->position; rect.y = e->start; rect.width = 1; rect.height = e->end - e->start; break; case TopEdge: case BottomEdge: default: rect.x = e->start; rect.y = e->position; rect.width = e->end - e->start; rect.height = 1; } // If the edge is in the window region, remove it, // if it's partly in the region, split it edgeRegion = XCreateRegion(); resultRegion = XCreateRegion(); XUnionRectWithRegion(&rect, edgeRegion, edgeRegion); XSubtractRegion(edgeRegion, c->region, resultRegion); if (XEmptyRegion(resultRegion)) remove = TRUE; else if (!XEqualRegion(edgeRegion, resultRegion)) { snapAddRegionEdges(sw, e, resultRegion); remove = TRUE; } next = e->next; if (remove) { if (e->prev == NULL) sw->edges = e->next; if (e->next == NULL) sw->reverseEdges = e->prev; snapRemoveEdge(e); remove = FALSE; } XDestroyRegion(resultRegion); XDestroyRegion(edgeRegion); } } } /* * Loop on outputDevs and add the extents as edges * Note that left side is a right edge, right side a left edge, * top side a bottom edge and bottom side a top edge, * since they will be snapped as the right/left/bottom/top edge of a window */ static void snapUpdateScreenEdges(CompWindow * w) { CompWindow *c = NULL; Edge *e = NULL, *next = NULL; SNAP_WINDOW(w); Region edgeRegion, resultRegion; XRectangle rect; Bool remove = FALSE; XRectangle area; int i; for (i = 0; i < w->screen->nOutputDev; i++) { screenGetOutputDevRect(w->screen, i, &area); snapAddEdge(&sw->edges, &sw->reverseEdges, 0, area.y, area.x, area.x + area.width - 1, BottomEdge, TRUE); snapAddEdge(&sw->edges, &sw->reverseEdges, 0, area.y + area.height, area.x, area.x + area.width - 1, TopEdge, TRUE); snapAddEdge(&sw->edges, &sw->reverseEdges, 0, area.x, area.y, area.y + area.height - 1, RightEdge, TRUE); snapAddEdge(&sw->edges, &sw->reverseEdges, 0, area.x + area.width, area.y, area.y + area.height - 1, LeftEdge, TRUE); } // Drop screen edges parts that are under struts, basically apply the // same strategy than for windows edges visibility for (c = w->screen->windows; c; c = c->next) { if (c == w || !c->struts) continue; for (e = sw->edges; e; e = next) { if (!e->screenEdge) { next = e->next; continue; } switch (e->type) { case LeftEdge: case RightEdge: rect.x = e->position; rect.y = e->start; rect.width = 1; rect.height = e->end - e->start; break; case TopEdge: case BottomEdge: default: rect.x = e->start; rect.y = e->position; rect.width = e->end - e->start; rect.height = 1; } edgeRegion = XCreateRegion(); resultRegion = XCreateRegion(); XUnionRectWithRegion(&rect, edgeRegion, edgeRegion); XSubtractRegion(edgeRegion, c->region, resultRegion); if (XEmptyRegion(resultRegion)) remove = TRUE; else if (!XEqualRegion(edgeRegion, resultRegion)) { snapAddRegionEdges(sw, e, resultRegion); remove = TRUE; } next = e->next; if (remove) { if (e->prev == NULL) sw->edges = e->next; if (e->next == NULL) sw->reverseEdges = e->prev; snapRemoveEdge(e); remove = FALSE; } XDestroyRegion(resultRegion); XDestroyRegion(edgeRegion); } } } /* * Clean edges and fill it again with appropriate edges */ static void snapUpdateEdges(CompWindow * w) { SNAP_SCREEN(w->screen); snapFreeEdges(w); snapUpdateWindowsEdges(w); if (ss->edgesCategories & ScreenEdges) snapUpdateScreenEdges(w); } // Edges checking functions (move) --------------------------------------------- /* * Find nearest edge in the direction set by "type", * w is the grabbed window, position/start/end are the window edges coordinates * before : if true the window has to be before the edge (top/left being origin) * snapDirection : just an helper, related to type */ static void snapMoveCheckNearestEdge(CompWindow * w, int position, int start, int end, Bool before, EdgeType type, int snapDirection) { SNAP_SCREEN(w->screen); SNAP_WINDOW(w); Edge *current = sw->edges; Edge *edge = current; int dist, min = 65535; while (current) { // Skip wrong type or outbound edges if (current->type != type || current->end < start || current->start > end) { current = current->next; continue; } // Compute distance dist = before ? position - current->position : current->position - position; // Update minimum distance if needed if (dist < min && dist >= 0) { min = dist; edge = current; } // 0-dist edge, just break if (dist == 0) break; // Unsnap edges that aren't snapped anymore if (current->snapped && dist > ss->resistanceDistance) current->snapped = FALSE; current = current->next; } // We found a 0-dist edge, or we have a snapping candidate if (min == 0 || (min <= ss->attractionDistance && ss->snapType & EdgeAttraction)) { // Update snapping data if (ss->snapType & EdgeResistance) { sw->snapped = TRUE; sw->snapDirection |= snapDirection; } // Attract the window if needed, moving it of the correct dist if (min != 0 && !edge->snapped) { edge->snapped = TRUE; switch (type) { case LeftEdge: snapMoveWindow(w, min, 0); break; case RightEdge: snapMoveWindow(w, -min, 0); break; case TopEdge: snapMoveWindow(w, 0, min); break; case BottomEdge: snapMoveWindow(w, 0, -min); break; default: break; } } } } /* * Call the previous function for each of the 4 sides of the window */ static void snapMoveCheckEdges(CompWindow * w) { snapMoveCheckNearestEdge(w, WIN_X(w), WIN_Y(w), WIN_Y(w) + WIN_H(w), TRUE, RightEdge, HorizontalSnap); snapMoveCheckNearestEdge(w, WIN_X(w) + WIN_W(w), WIN_Y(w), WIN_Y(w) + WIN_H(w), FALSE, LeftEdge, HorizontalSnap); snapMoveCheckNearestEdge(w, WIN_Y(w), WIN_X(w), WIN_X(w) + WIN_W(w), TRUE, BottomEdge, VerticalSnap); snapMoveCheckNearestEdge(w, WIN_Y(w) + WIN_H(w), WIN_X(w), WIN_X(w) + WIN_W(w), FALSE, TopEdge, VerticalSnap); } // Edges checking functions (resize) ------------------------------------------- /* * Similar function for Snap on Resize */ static void snapResizeCheckNearestEdge(CompWindow * w, int position, int start, int end, Bool before, EdgeType type, int snapDirection, Bool preview) { SNAP_SCREEN(w->screen); SNAP_WINDOW(w); Edge *current = sw->edges; Edge *edge = current; int dist, min = 65535; while (current) { // Skip wrong type or outbound edges if (current->type != type || current->end < start || current->start > end) { current = current->next; continue; } // Compute distance dist = before ? position - current->position : current->position - position; // Update minimum distance if needed if (dist < min && dist >= 0) { min = dist; edge = current; } // 0-dist edge, just break if (dist == 0) break; // Unsnap edges that aren't snapped anymore if (current->snapped && dist > ss->resistanceDistance) current->snapped = FALSE; current = current->next; } // We found a 0-dist edge, or we have a snapping candidate if (min == 0 || (min <= ss->attractionDistance && ss->snapType & EdgeAttraction)) { // Update snapping data if (ss->snapType & EdgeResistance) { sw->snapped = TRUE; sw->snapDirection |= snapDirection; } // FIXME : this needs resize-specific code. // Attract the window if needed, moving it of the correct dist if (min != 0 && !edge->snapped) { edge->snapped = TRUE; switch (type) { case LeftEdge: snapResizeWindow(w, min, 0, 0, 0, preview); break; case RightEdge: snapResizeWindow(w, -min, 0, 0, 0, preview); break; case TopEdge: snapResizeWindow(w, 0, min, 0, 0, preview); break; case BottomEdge: snapResizeWindow(w, 0, -min, 0, 0, preview); break; default: break; } } } } /* * Call the previous function for each of the 4 sides of the window */ static void snapResizeCheckEdges(CompWindow * w, int dx, int dy, int dw, int dh, Bool preview) { int x, y, width, height; x = preview ? WIN_X(w) + dx : WIN_W(w); y = preview ? WIN_Y(w) + dy : WIN_Y(w); width = preview ? WIN_W(w) + dw : WIN_W(w); height = preview ? WIN_H(w) + dh : WIN_H(w); snapResizeCheckNearestEdge(w, x, y, y + height, TRUE, RightEdge, HorizontalSnap, preview); snapResizeCheckNearestEdge(w, x + width, y, y + height, FALSE, LeftEdge, HorizontalSnap, preview); snapResizeCheckNearestEdge(w, y, x, x + width, TRUE, BottomEdge, VerticalSnap, preview); snapResizeCheckNearestEdge(w, y + height, x, x + width, FALSE, TopEdge, VerticalSnap, preview); } // avoidSnap functions --------------------------------------------------------- static Bool snapEnableSnapping(CompDisplay * d, CompAction * action, CompActionState state, CompOption * option, int nOption) { SNAP_DISPLAY(d); sd->snapping = TRUE; return FALSE; } static Bool snapDisableSnapping(CompDisplay * d, CompAction * action, CompActionState state, CompOption * option, int nOption) { SNAP_DISPLAY(d); sd->snapping = FALSE; return FALSE; } // Check if avoidSnap is matched, and enable/disable snap consequently static void snapHandleEvent(CompDisplay * d, XEvent * event) { SNAP_DISPLAY(d); if (event->type == d->xkbEvent) { XkbAnyEvent *xkbEvent = (XkbAnyEvent *) event; if (xkbEvent->xkb_type == XkbStateNotify) { XkbStateNotifyEvent *stateEvent = (XkbStateNotifyEvent *) event; unsigned int mods = 0xffffffff; if (sd->avoidSnapMask) mods = sd->avoidSnapMask; if ((stateEvent->mods & mods) == mods) snapDisableSnapping(d, NULL, 0, NULL, 0); else snapEnableSnapping(d, NULL, 0, NULL, 0); } } UNWRAP(sd, d, handleEvent); (*d->handleEvent) (d, event); WRAP(sd, d, handleEvent, snapHandleEvent); } // Events notifications -------------------------------------------------------- static void snapWindowResizeNotify(CompWindow * w, int dx, int dy, int dw, int dh, Bool preview) { SNAP_DISPLAY (w->screen->display); SNAP_SCREEN(w->screen); SNAP_WINDOW (w); UNWRAP(ss, w->screen, windowResizeNotify); (*w->screen->windowResizeNotify) (w, dx, dy, dw, dh, preview); WRAP(ss, w->screen, windowResizeNotify, snapWindowResizeNotify); // avoid-infinite-notify-loop mode/not grabbed if (sw->skipNotify || !(sw->grabbed & ResizeGrab)) return; // we have to avoid snapping but there's still some buffered moving if (!sd->snapping && (sw->dx || sw->dy || sw->dw || sw->dh)) { snapResizeWindow(w, sw->dx, sw->dy, sw->dw, sw->dh, preview); sw->dx = sw->dy = sw->dw = sw->dh = 0; return; } // avoiding snap, nothing buffered if (!sd->snapping) return; // apply edge resistance if (ss->snapType & EdgeResistance) { // If there's horizontal snapping, add dx to current buffered // dx and resist (move by -dx) or release the window and move // by buffered dx - dx, same for dh if (sw->snapped && sw->snapDirection & HorizontalSnap) { sw->dx += dx; if (sw->dx < ss->resistanceDistance && sw->dx > -ss->resistanceDistance) snapResizeWindow(w, -dx, 0, 0, 0, preview); else { snapResizeWindow(w, sw->dx - dx, 0, 0, 0, preview); sw->dx = 0; if (!sw->dw) sw->snapDirection &= VerticalSnap; } sw->dw += dw; if (sw->dw < ss->resistanceDistance && sw->dw > -ss->resistanceDistance) snapResizeWindow(w, 0, 0, -dw, 0, preview); else { snapResizeWindow(w, 0, 0, sw->dw - dw, 0, preview); sw->dw = 0; if (!sw->dx) sw->snapDirection &= VerticalSnap; } } // Same for vertical snapping and dy/dh if (sw->snapped && sw->snapDirection & VerticalSnap) { sw->dy += dy; if (sw->dy < ss->resistanceDistance && sw->dy > -ss->resistanceDistance) snapResizeWindow(w, 0, -dy, 0, 0, preview); else { snapResizeWindow(w, 0, sw->dy - dy, 0, 0, preview); sw->dy = 0; if (!sw->dh) sw->snapDirection &= HorizontalSnap; } sw->dh += dh; if (sw->dh < ss->resistanceDistance && sw->dh > -ss->resistanceDistance) snapResizeWindow(w, 0, 0, 0, -dh, preview); else { snapResizeWindow(w, 0, 0, 0, sw->dh - dh, preview); sw->dh = 0; if (!sw->dy) sw->snapDirection &= HorizontalSnap; } } // If we are no longer snapping in any direction, reset snapped if (sw->snapped && !sw->snapDirection) sw->snapped = FALSE; } // If we don't already snap vertically and horizontally, // check edges status if (sw->snapDirection != (VerticalSnap | HorizontalSnap)) snapResizeCheckEdges(w, dx, dy, dw, dh, preview); } static void snapWindowMoveNotify(CompWindow * w, int dx, int dy, Bool immediate) { SNAP_DISPLAY(w->screen->display); SNAP_SCREEN(w->screen); SNAP_WINDOW(w); UNWRAP(ss, w->screen, windowMoveNotify); (*w->screen->windowMoveNotify) (w, dx, dy, immediate); WRAP(ss, w->screen, windowMoveNotify, snapWindowMoveNotify); // avoid-infinite-notify-loop mode/not grabbed if (sw->skipNotify || !(sw->grabbed & MoveGrab)) return; // we have to avoid snapping but there's still some buffered moving if (!sd->snapping && (sw->dx || sw->dy)) { snapMoveWindow(w, sw->dx, sw->dy); sw->dx = sw->dy = 0; return; } // avoiding snap, nothing buffered if (!sd->snapping) return; // apply edge resistance if (ss->snapType & EdgeResistance) { // If there's horizontal snapping, add dx to current buffered // dx and resist (move by -dx) or release the window and move // by buffered dx - dx if (sw->snapped && sw->snapDirection & HorizontalSnap) { sw->dx += dx; if (sw->dx < ss->resistanceDistance && sw->dx > -ss->resistanceDistance) snapMoveWindow(w, -dx, 0); else { snapMoveWindow(w, sw->dx - dx, 0); sw->dx = 0; sw->snapDirection &= VerticalSnap; } } // Same for vertical snapping and dy if (sw->snapped && sw->snapDirection & VerticalSnap) { sw->dy += dy; if (sw->dy < ss->resistanceDistance && sw->dy > -ss->resistanceDistance) snapMoveWindow(w, 0, -dy); else { snapMoveWindow(w, 0, sw->dy - dy); sw->dy = 0; sw->snapDirection &= HorizontalSnap; } } // If we are no longer snapping in any direction, reset snapped if (sw->snapped && !sw->snapDirection) sw->snapped = FALSE; } // If we don't already snap vertically and horizontally, // check edges status if (sw->snapDirection != (VerticalSnap | HorizontalSnap)) snapMoveCheckEdges(w); } /* * Initiate snap, get edges */ static void snapWindowGrabNotify(CompWindow * w, int x, int y, unsigned int state, unsigned int mask) { SNAP_SCREEN(w->screen); SNAP_WINDOW(w); sw->grabbed = (mask & CompWindowGrabResizeMask) ? ResizeGrab : MoveGrab; snapUpdateEdges(w); UNWRAP(ss, w->screen, windowGrabNotify); (*w->screen->windowGrabNotify) (w, x, y, state, mask); WRAP(ss, w->screen, windowGrabNotify, snapWindowGrabNotify); } /* * Clean edges data, reset dx/dy to avoid buggy moves * when snap will be triggered again. */ static void snapWindowUngrabNotify(CompWindow * w) { SNAP_SCREEN(w->screen); SNAP_WINDOW(w); snapFreeEdges(w); sw->snapped = FALSE; sw->snapDirection = 0; sw->grabbed = 0; sw->dx = sw->dy = sw->dw = sw->dh = 0; UNWRAP(ss, w->screen, windowUngrabNotify); (*w->screen->windowUngrabNotify) (w); WRAP(ss, w->screen, windowUngrabNotify, snapWindowUngrabNotify); } // Internal stuff -------------------------------------------------------------- static Bool snapSetScreenOption(CompScreen * screen, char *name, CompOptionValue * value) { CompOption *o; int index; SNAP_SCREEN(screen); o = compFindOption(ss->opt, NUM_OPTIONS(ss), name, &index); if (!o) return FALSE; switch (index) { case SNAP_SCREEN_OPTION_SNAP_TYPE: if (compSetStringOption(o, value)) { int i; SnapType type = SNAP_TYPE_OPTION_DEFAULT; for (i = 0; i < o->rest.s.nString; i++) if (strcmp(snapTypes[i], o->value.s) == 0) type = (SnapType) i; switch (type) { case SnapTypeFull: ss->snapType = EdgeResistance | EdgeAttraction; break; default: case SnapTypeResistance: ss->snapType = EdgeResistance; break; } return TRUE; } break; case SNAP_SCREEN_OPTION_EDGES_CATEGORIES: if (compSetStringOption(o, value)) { int i; EdgesCategory type = EDGES_CATEGORY_OPTION_DEFAULT; for (i = 0; i < o->rest.s.nString; i++) if (strcmp(edgesCategories[i], o->value.s) == 0) type = (EdgesCategory) i; switch (type) { case EdgesCategoryScreen: ss->edgesCategories = ScreenEdges; break; case EdgesCategoryWindows: ss->edgesCategories = WindowsEdges; break; default: case EdgesCategoryBoth: ss->edgesCategories = ScreenEdges | WindowsEdges; break; } return TRUE; } break; case SNAP_SCREEN_OPTION_RESISTANCE_DISTANCE: if (compSetIntOption(o, value)) { ss->resistanceDistance = o->value.i; return TRUE; } break; case SNAP_SCREEN_OPTION_ATTRACTION_DISTANCE: if (compSetIntOption(o, value)) { ss->attractionDistance = o->value.i; return TRUE; } break; default: break; } return FALSE; } static void snapScreenInitOptions(SnapScreen * ss) { CompOption *o; o = &ss->opt[SNAP_SCREEN_OPTION_SNAP_TYPE]; o->advanced = False; o->name = "snap_type"; o->group = N_("Behaviour"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Snap Type"); o->longDesc = N_("Choose 'Resistance', 'Attraction' or 'Full Mode'."); o->type = CompOptionTypeString; o->value.s = strdup(snapTypes[SNAP_TYPE_OPTION_DEFAULT]); o->rest.s.string = snapTypes; o->rest.s.nString = NUM_SNAP_TYPES; o = &ss->opt[SNAP_SCREEN_OPTION_EDGES_CATEGORIES]; o->advanced = False; o->name = "edges_categories"; o->group = N_("Behaviour"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Edges"); o->longDesc = N_("Snap to 'Screen Edges', 'Windows Edges' or 'Both'"); o->type = CompOptionTypeString; o->value.s = strdup(edgesCategories[EDGES_CATEGORY_OPTION_DEFAULT]); o->rest.s.string = edgesCategories; o->rest.s.nString = NUM_EDGES_CATEGORIES; o = &ss->opt[SNAP_SCREEN_OPTION_RESISTANCE_DISTANCE]; o->advanced = False; o->name = "resistance_distance"; o->group = N_("Behaviour"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = "Edge Resistance Distance"; o->longDesc = "Edge Resistance Distance."; o->type = CompOptionTypeInt; o->value.i = SNAP_RESISTANCE_DISTANCE_DEFAULT; o->rest.i.min = SNAP_EDGE_DISTANCE_MIN; o->rest.i.max = SNAP_EDGE_DISTANCE_MAX; o = &ss->opt[SNAP_SCREEN_OPTION_ATTRACTION_DISTANCE]; o->advanced = False; o->name = "attraction_distance"; o->group = N_("Behaviour"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = "Edge Attraction Distance"; o->longDesc = "Edge Attraction Distance."; o->type = CompOptionTypeInt; o->value.i = SNAP_ATTRACTION_DISTANCE_DEFAULT; o->rest.i.min = SNAP_EDGE_DISTANCE_MIN; o->rest.i.max = SNAP_EDGE_DISTANCE_MAX; } static CompOption *snapGetScreenOptions(CompScreen * screen, int *count) { if (screen) { SNAP_SCREEN(screen); *count = NUM_OPTIONS(ss); return ss->opt; } else { SnapScreen *ss = malloc(sizeof(SnapScreen)); snapScreenInitOptions(ss); *count = NUM_OPTIONS(ss); return ss->opt; } } static Bool snapSetDisplayOption(CompDisplay * display, char *name, CompOptionValue * value) { CompOption *o; int index; SNAP_DISPLAY(display); o = compFindOption(sd->opt, NUM_OPTIONS(sd), name, &index); if (!o) return FALSE; switch (index) { case SNAP_DISPLAY_OPTION_AVOID_SNAP: if (compSetOptionList(o, value)) { int i; sd->avoidSnapMask = 0; for (i = 0; i < o->value.list.nValue; i++) { int j; for (j = 0; j < nMods; j++) if (strcmp(o->value.list.value[i].s, Mods[j]) == 0) sd->avoidSnapMask |= ModMask[j]; } return TRUE; } break; default: break; } return FALSE; } static void snapDisplayInitOptions(SnapDisplay * sd) { CompOption *o; CompOptionValue *v; o = &sd->opt[SNAP_DISPLAY_OPTION_AVOID_SNAP]; o->name = "avoid_snap"; o->group = N_("Bindings"); o->subGroup = N_(""); o->displayHints = ""; o->shortDesc = N_("Avoid Snap Modifier"); o->longDesc = N_("Use this binding to Avoid Snapping."); o->type = CompOptionTypeList; v = malloc(sizeof(CompOptionValue)); o->value.list.type = CompOptionTypeString; o->value.list.nValue = 1; o->value.list.value = v; v->s = strdup(N_("Shift")); o->rest.s.nString = nMods; o->rest.s.string = Mods; } static CompOption *snapGetDisplayOptions(CompDisplay * display, int *count) { if (display) { SNAP_DISPLAY(display); *count = NUM_OPTIONS(sd); return sd->opt; } else { SnapDisplay *sd = malloc(sizeof(SnapDisplay)); snapDisplayInitOptions(sd); *count = NUM_OPTIONS(sd); return sd->opt; } } static Bool snapInitDisplay(CompPlugin * p, CompDisplay * d) { SnapDisplay *sd; sd = malloc(sizeof(SnapDisplay)); if (!sd) return FALSE; sd->screenPrivateIndex = allocateScreenPrivateIndex(d); if (sd->screenPrivateIndex < 0) { free(sd); return FALSE; } WRAP(sd, d, handleEvent, snapHandleEvent); sd->avoidSnapMask = AVOID_SNAP_MODIFIERS_DEFAULT; sd->snapping = TRUE; snapDisplayInitOptions(sd); d->privates[displayPrivateIndex].ptr = sd; return TRUE; } static void snapFiniDisplay(CompPlugin * p, CompDisplay * d) { SNAP_DISPLAY(d); freeScreenPrivateIndex(d, sd->screenPrivateIndex); UNWRAP(sd, d, handleEvent); free(sd); } static Bool snapInitScreen(CompPlugin * p, CompScreen * s) { SnapScreen *ss; SNAP_DISPLAY(s->display); ss = malloc(sizeof(SnapScreen)); if (!ss) return FALSE; ss->windowPrivateIndex = allocateWindowPrivateIndex(s); if (ss->windowPrivateIndex < 0) { free(ss); return FALSE; } ss->snapType = SNAP_TYPE_DEFAULT; ss->edgesCategories = EDGES_CATEGORIES_DEFAULT; ss->resistanceDistance = SNAP_RESISTANCE_DISTANCE_DEFAULT; ss->attractionDistance = SNAP_ATTRACTION_DISTANCE_DEFAULT; snapScreenInitOptions(ss); //WRAP(ss, s, windowResizeNotify, snapWindowResizeNotify); WRAP(ss, s, windowMoveNotify, snapWindowMoveNotify); WRAP(ss, s, windowGrabNotify, snapWindowGrabNotify); WRAP(ss, s, windowUngrabNotify, snapWindowUngrabNotify); s->privates[sd->screenPrivateIndex].ptr = ss; return TRUE; } static void snapFiniScreen(CompPlugin * p, CompScreen * s) { SNAP_SCREEN(s); freeWindowPrivateIndex(s, ss->windowPrivateIndex); //UNWRAP(ss, s, windowResizeNotify); UNWRAP(ss, s, windowMoveNotify); UNWRAP(ss, s, windowGrabNotify); UNWRAP(ss, s, windowUngrabNotify); free(ss); } static Bool snapInitWindow(CompPlugin * p, CompWindow * w) { SnapWindow *sw; SNAP_SCREEN(w->screen); sw = malloc(sizeof(SnapWindow)); if (!sw) return FALSE; sw->edges = sw->reverseEdges = NULL; sw->snapDirection = 0; sw->dx = sw->dy = sw->dw = sw->dh = 0; sw->grabbed = 0; sw->snapped = FALSE; sw->skipNotify = FALSE; w->privates[ss->windowPrivateIndex].ptr = sw; return TRUE; } static void snapFiniWindow(CompPlugin * p, CompWindow * w) { SNAP_WINDOW(w); snapFreeEdges(w); free(sw); } static Bool snapInit(CompPlugin * p) { displayPrivateIndex = allocateDisplayPrivateIndex(); if (displayPrivateIndex < 0) return FALSE; return TRUE; } static void snapFini(CompPlugin * p) { if (displayPrivateIndex >= 0) freeDisplayPrivateIndex(displayPrivateIndex); } CompPluginDep snapDeps[] = { {CompPluginRuleAfter, "decoration"} }; CompPluginFeature snapFeatures[] = { {"edgeresistance"} }; CompPluginVTable snapVTable = { "snap", N_("Snapping Windows"), N_("Enables windows edges resistance"), snapInit, snapFini, snapInitDisplay, snapFiniDisplay, snapInitScreen, snapFiniScreen, snapInitWindow, snapFiniWindow, snapGetDisplayOptions, snapSetDisplayOption, snapGetScreenOptions, snapSetScreenOption, snapDeps, sizeof(snapDeps) / sizeof(snapDeps[0]), snapFeatures, sizeof(snapFeatures) / sizeof(snapFeatures[0]), BERYL_ABI_INFO, "beryl-plugins", "wm", 0, 0, False, }; CompPluginVTable *getCompPluginInfo(void) { return &snapVTable; }