/*
* 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 <stdlib.h>
#include <string.h>
#include <math.h>
#include <beryl.h>
/*
* 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;
}
syntax highlighted by Code2HTML, v. 0.9.1