3
* Author : Guillaume "iXce" Seguin
4
* Email : ixce@beryl-project.org
6
* Ported to compiz by : Patrick "marex" Niklaus
7
* Email : marex@beryl-project.org
9
* Ported to C++ by : Travis Watkins
10
* Email : amaranth@ubuntu.com
12
* Copyright (C) 2009 Guillaume Seguin
14
* This program is free software; you can redistribute it and/or
15
* modify it under the terms of the GNU General Public License
16
* as published by the Free Software Foundation; either version 2
17
* of the License, or (at your option) any later version.
19
* This program is distributed in the hope that it will be useful,
20
* but WITHOUT ANY WARRANTY; without even the implied warranty of
21
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
* GNU General Public License for more details.
24
* You should have received a copy of the GNU General Public License
25
* along with this program; if not, write to the Free Software
26
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31
* - Apply Edge Resistance to resize
41
COMPIZ_PLUGIN_20090315 (snap, SnapPluginVTable);
46
* Wrapper functions to avoid infinite notify loops
49
SnapWindow::move (int dx, int dy)
52
window->move (dx, dy, true);
53
screen->warpPointer (dx, dy);
58
SnapWindow::resize (int dx, int dy, int dwidth, int dheight)
60
CompWindow::Geometry geometry = window->geometry ();
62
window->resize (geometry.x () + dx, geometry.y () + dy,
63
geometry.width () + dwidth, geometry.height () + dheight,
69
SnapWindow::addEdge (Window id,
78
edge.position = position;
82
edge.screenEdge = screenEdge;
87
edges.push_back (edge);
91
* Add an edge for each rectangle of the region
94
SnapWindow::addRegionEdges (Edge *parent, CompRegion region)
96
int position, start, end;
98
foreach (const CompRect &r, region.rects ())
100
switch (parent->type)
116
addEdge (parent->id, position, start, end,
117
parent->type, parent->screenEdge);
118
edges.back ().passed = parent->passed;
122
/* Checks if a window is considered a snap window. If it's
123
* not visible, returns false. If it's a panel and we're
124
* snapping to screen edges, it's considered a snap-window.
127
#define UNLIKELY(x) __builtin_expect(!!(x),0)
130
isSnapWindow (CompWindow *w)
132
SNAP_SCREEN (screen);
136
if (!w->isViewable ())
138
if ((w->type () & SNAP_WINDOW_TYPE) &&
139
(ss->optionGetEdgesCategoriesMask () & EdgesCategoriesWindowEdgesMask))
142
(ss->optionGetEdgesCategoriesMask () & EdgesCategoriesScreenEdgesMask))
147
// Edges update functions ------------------------------------------------------
149
* Detect visible windows edges
152
SnapWindow::updateWindowsEdges ()
154
CompRegion edgeRegion, resultRegion;
158
// First add all the windows
159
foreach (CompWindow *w, screen->windows ())
162
// Just check that we're not trying to snap to current window,
163
// that the window is not invisible and of a valid type
164
if (w == window || !isSnapWindow (w))
169
input = w->borderRect ();
170
addEdge (w->id (), input.top (), input.left (),
171
input.right (), TopEdge, false);
172
addEdge (w->id (), input.bottom (), input.left (),
173
input.right (), BottomEdge, false);
174
addEdge (w->id (), input.left (), input.top (),
175
input.bottom (), LeftEdge, false);
176
addEdge (w->id (), input.right (), input.top (),
177
input.bottom (), RightEdge, false);
180
// Now strip invisible edges
181
// Loop through all the windows stack, and through all the edges
182
// If an edge has been passed, check if it's in the region window,
183
// if the edge is fully under the window, drop it, or if it's only
184
// partly covered, cut it/split it in one/two smaller visible edges
185
foreach (CompWindow *w, screen->windows ())
187
if (w == window || !isSnapWindow (w))
190
// can't use foreach here because we need the iterator for erase()
191
for (std::list<Edge>::iterator it = edges.begin (); it != edges.end (); )
198
if (e->id == w->id ())
208
rect.setGeometry (e->position,
216
rect.setGeometry (e->start,
222
// If the edge is in the window region, remove it,
223
// if it's partly in the region, split it
224
edgeRegion = CompRegion (rect);
225
resultRegion = edgeRegion - w->region ();
226
if (resultRegion.isEmpty ())
230
else if (edgeRegion != resultRegion)
232
addRegionEdges (e, resultRegion);
238
it = edges.erase (it);
250
* Loop on outputDevs and add the extents as edges
251
* Note that left side is a right edge, right side a left edge,
252
* top side a bottom edge and bottom side a top edge,
253
* since they will be snapped as the right/left/bottom/top edge of a window
256
SnapWindow::updateScreenEdges ()
258
CompRegion edgeRegion, resultRegion;
261
foreach (CompOutput output, screen->outputDevs ())
263
const CompRect& area = output.workArea ();
264
addEdge (0, area.top (), area.left (), area.right () - 1,
266
addEdge (0, area.bottom (), area.left (), area.right () - 1,
268
addEdge (0, area.left (), area.top (), area.bottom () - 1,
270
addEdge (0, area.right (), area.top (), area.bottom () - 1,
274
// Drop screen edges parts that are under struts, basically apply the
275
// same strategy than for windows edges visibility
276
foreach (CompWindow *w, screen->windows ())
278
if (w == window || !w->struts ())
281
for (std::list<Edge>::iterator it = edges.begin (); it != edges.end ();)
296
rect.setGeometry (e->position,
304
rect.setGeometry (e->start,
310
edgeRegion = CompRegion (rect);
311
resultRegion = edgeRegion - w->region ();
312
if (resultRegion.isEmpty ())
316
else if (edgeRegion != resultRegion)
318
addRegionEdges (e, resultRegion);
324
it = edges.erase (it);
336
* Clean edges and fill it again with appropriate edges
339
SnapWindow::updateEdges ()
341
SNAP_SCREEN (screen);
344
updateWindowsEdges ();
346
if (ss->optionGetEdgesCategoriesMask () & EdgesCategoriesScreenEdgesMask)
347
updateScreenEdges ();
350
// Edges checking functions (move) ---------------------------------------------
353
* Find nearest edge in the direction set by "type",
354
* w is the grabbed window, position/start/end are the window edges coordinates
355
* before : if true the window has to be before the edge (top/left being origin)
356
* snapDirection : just an helper, related to type
359
SnapWindow::moveCheckNearestEdge (int position,
366
SNAP_SCREEN (screen);
368
Edge *edge = &edges.front ();
369
int dist, min = 65535;
371
foreach (Edge ¤t, edges)
373
// Skip wrong type or outbound edges
374
if (current.type != type || current.end < start || current.start > end)
378
dist = before ? position - current.position : current.position - position;
379
// Update minimum distance if needed
380
if (dist < min && dist >= 0)
385
// 0-dist edge, just break
388
// Unsnap edges that aren't snapped anymore
389
if (current.snapped && dist > ss->optionGetResistanceDistance ())
390
current.snapped = false;
392
// We found a 0-dist edge, or we have a snapping candidate
393
if (min == 0 || (min <= ss->optionGetAttractionDistance ()
394
&& ss->optionGetSnapTypeMask () & SnapTypeEdgeAttractionMask))
396
// Update snapping data
397
if (ss->optionGetSnapTypeMask () & SnapTypeEdgeResistanceMask)
399
snapGeometry = window->geometry ();
400
this->snapDirection |= snapDirection;
402
// Attract the window if needed, moving it of the correct dist
403
if (min != 0 && !edge->snapped)
405
edge->snapped = true;
428
* Call the previous function for each of the 4 sides of the window
431
SnapWindow::moveCheckEdges ()
433
CompRect input (window->borderRect ());
434
moveCheckNearestEdge (input.left (), input.top (), input.bottom (),
435
true, RightEdge, HorizontalSnap);
436
moveCheckNearestEdge (input.right (), input.top (), input.bottom (),
437
false, LeftEdge, HorizontalSnap);
438
moveCheckNearestEdge (input.top (), input.left (), input.right (),
439
true, BottomEdge, VerticalSnap);
440
moveCheckNearestEdge (input.bottom (), input.left (), input.right (),
441
false, TopEdge, VerticalSnap);
444
// Edges checking functions (resize) -------------------------------------------
447
* Similar function for Snap on Resize
450
SnapWindow::resizeCheckNearestEdge (int position,
457
SNAP_SCREEN (screen);
459
Edge *edge = &edges.front ();
460
int dist, min = 65535;
462
foreach (Edge ¤t, edges)
464
// Skip wrong type or outbound edges
465
if (current.type != type || current.end < start || current.start > end)
469
dist = before ? position - current.position : current.position - position;
470
// Update minimum distance if needed
471
if (dist < min && dist >= 0)
476
// 0-dist edge, just break
479
// Unsnap edges that aren't snapped anymore
480
if (current.snapped && dist > ss->optionGetResistanceDistance ())
481
current.snapped = false;
483
// We found a 0-dist edge, or we have a snapping candidate
484
if (min == 0 || (min <= ss->optionGetAttractionDistance ()
485
&& ss->optionGetSnapTypeMask () & SnapTypeEdgeAttractionMask))
487
// Update snapping data
488
if (ss->optionGetSnapTypeMask () & SnapTypeEdgeResistanceMask)
490
snapGeometry = window->serverGeometry ();
491
this->snapDirection |= snapDirection;
493
// FIXME : this needs resize-specific code.
494
// Attract the window if needed, moving it of the correct dist
495
if (min != 0 && !edge->snapped)
497
edge->snapped = true;
501
resize (min, 0, 0, 0);
504
resize (-min, 0, 0, 0);
507
resize (0, min, 0, 0);
510
resize (0, -min, 0, 0);
520
* Call the previous function for each of the 4 sides of the window
523
SnapWindow::resizeCheckEdges (int dx, int dy, int dwidth, int dheight)
525
CompRect input (window->borderRect ());
527
resizeCheckNearestEdge (input.left (), input.top (), input.bottom (),
528
true, RightEdge, HorizontalSnap);
529
resizeCheckNearestEdge (input.right (), input.top (), input.bottom (),
530
false, LeftEdge, HorizontalSnap);
531
resizeCheckNearestEdge (input.top (), input.left (), input.right (),
532
true, BottomEdge, VerticalSnap);
533
resizeCheckNearestEdge (input.bottom (), input.left (), input.right (),
534
false, TopEdge, VerticalSnap);
537
// Check if avoidSnap is matched, and enable/disable snap consequently
539
SnapScreen::handleEvent (XEvent *event)
541
if (event->type == screen->xkbEvent ())
543
XkbAnyEvent *xkbEvent = (XkbAnyEvent *) event;
545
if (xkbEvent->xkb_type == XkbStateNotify)
547
XkbStateNotifyEvent *stateEvent = (XkbStateNotifyEvent *) event;
549
unsigned int mods = 0xffffffff;
551
mods = avoidSnapMask;
553
if ((stateEvent->mods & mods) == mods)
560
screen->handleEvent (event);
563
// Events notifications --------------------------------------------------------
566
SnapWindow::resizeNotify (int dx, int dy, int dwidth, int dheight)
568
SNAP_SCREEN (screen);
570
window->resizeNotify (dx, dy, dwidth, dheight);
572
// avoid-infinite-notify-loop mode/not grabbed
573
if (skipNotify || !(grabbed & ResizeGrab))
576
// we have to avoid snapping but there's still some buffered moving
577
if (!ss->snapping && (m_dx || m_dy || m_dwidth || m_dheight))
579
resize (m_dx, m_dy, m_dwidth, m_dheight);
580
m_dx = m_dy = m_dwidth = m_dheight = 0;
584
// don't snap maximized windows
585
if (window->state () & CompWindowStateMaximizedHorzMask)
588
if (window->state () & CompWindowStateMaximizedVertMask)
591
// avoiding snap, nothing buffered
595
// apply edge resistance
596
if (ss->optionGetSnapTypeMask () & SnapTypeEdgeResistanceMask)
598
// If there's horizontal snapping, add dx to current buffered
599
// dx and resist (move by -dx) or release the window and move
600
// by buffered dx - dx, same for dh
601
if (!snapGeometry.isEmpty () && snapDirection & HorizontalSnap)
604
if (m_dx < ss->optionGetResistanceDistance ()
605
&& m_dx > -ss->optionGetResistanceDistance ())
607
resize (-dx, 0, 0, 0);
611
resize (m_dx - dx, 0, 0, 0);
614
snapDirection &= VerticalSnap;
617
if (m_dwidth < ss->optionGetResistanceDistance ()
618
&& m_dwidth > -ss->optionGetResistanceDistance ())
620
resize (0, 0, -dwidth, 0);
624
resize (0, 0, m_dwidth - dwidth, 0);
627
snapDirection &= VerticalSnap;
631
// Same for vertical snapping and dy/dh
632
if (snapGeometry.isEmpty () && snapDirection & VerticalSnap)
635
if (m_dy < ss->optionGetResistanceDistance ()
636
&& m_dy > -ss->optionGetResistanceDistance ())
638
resize (0, -dy, 0, 0);
642
resize (0, m_dy - dy, 0, 0);
645
snapDirection &= HorizontalSnap;
647
m_dheight += dheight;
648
if (m_dheight < ss->optionGetResistanceDistance ()
649
&& m_dheight > -ss->optionGetResistanceDistance ())
651
resize (0, 0, 0, -dheight);
655
resize (0, 0, 0, m_dheight - dheight);
658
snapDirection &= HorizontalSnap;
661
// If we are no longer snapping in any direction, reset snapped
662
if (!snapGeometry.isEmpty () && !snapDirection)
663
snapGeometry = CompWindow::Geometry ();
666
// If we don't already snap vertically and horizontally,
667
// check edges status
668
if (snapDirection != (VerticalSnap | HorizontalSnap))
669
resizeCheckEdges (dx, dy, dwidth, dheight);
673
SnapWindow::moveNotify (int dx, int dy, bool immediate)
675
SNAP_SCREEN (screen);
677
window->moveNotify (dx, dy, immediate);
679
// avoid-infinite-notify-loop mode/not grabbed
680
if (skipNotify || !(grabbed & MoveGrab))
683
// we have to avoid snapping but there's still some buffered moving
684
if (!ss->snapping && (m_dx || m_dy))
691
// don't snap maximized windows
692
if (window->state () & CompWindowStateMaximizedHorzMask)
695
if (window->state () & CompWindowStateMaximizedVertMask)
698
// avoiding snap, nothing buffered
702
// apply edge resistance
703
if (ss->optionGetSnapTypeMask () & SnapTypeEdgeResistanceMask)
705
// If there's horizontal snapping, add dx to current buffered
706
// dx and resist (move by -dx) or release the window and move
707
// by buffered dx - dx
708
if (!snapGeometry.isEmpty () && snapDirection & HorizontalSnap)
711
if (m_dx < ss->optionGetResistanceDistance ()
712
&& m_dx > -ss->optionGetResistanceDistance ())
714
dx = snapGeometry.x () - window->geometry ().x ();
721
snapDirection &= VerticalSnap;
724
// Same for vertical snapping and dy
725
if (!snapGeometry.isEmpty () && snapDirection & VerticalSnap)
728
if (m_dy < ss->optionGetResistanceDistance ()
729
&& m_dy > -ss->optionGetResistanceDistance ())
731
dy = snapGeometry.y () - window->geometry ().y ();
738
snapDirection &= HorizontalSnap;
741
// If we are no longer snapping in any direction, reset snapped
742
if (!snapGeometry.isEmpty () && !snapDirection)
743
snapGeometry = CompWindow::Geometry ();
745
// If we don't already snap vertically and horizontally,
746
// check edges status
747
if (snapDirection != (VerticalSnap | HorizontalSnap))
752
* Initiate snap, get edges
755
SnapWindow::grabNotify (int x, int y, unsigned int state, unsigned int mask)
757
grabbed = (mask & CompWindowGrabResizeMask) ? ResizeGrab : MoveGrab;
760
window->grabNotify (x, y, state, mask);
764
* Clean edges data, reset dx/dy to avoid buggy moves
765
* when snap will be triggered again.
768
SnapWindow::ungrabNotify ()
772
snapGeometry = CompWindow::Geometry ();
775
m_dx = m_dy = m_dwidth = m_dheight = 0;
777
window->ungrabNotify ();
780
// Internal stuff --------------------------------------------------------------
783
SnapScreen::optionChanged (CompOption *opt, SnapOptions::Options num)
787
case SnapOptions::AvoidSnap:
789
unsigned int mask = optionGetAvoidSnapMask ();
791
if (mask & AvoidSnapShiftMask)
792
avoidSnapMask |= ShiftMask;
793
if (mask & AvoidSnapAltMask)
794
avoidSnapMask |= CompAltMask;
795
if (mask & AvoidSnapControlMask)
796
avoidSnapMask |= ControlMask;
797
if (mask & AvoidSnapMetaMask)
798
avoidSnapMask |= CompMetaMask;
806
SnapScreen::SnapScreen (CompScreen *screen) :
807
PluginClassHandler <SnapScreen, CompScreen> (screen),
812
ScreenInterface::setHandler (screen);
814
#define setNotify(func) \
815
optionSet##func##Notify (boost::bind (&SnapScreen::optionChanged, this, _1, _2))
817
setNotify (AvoidSnap);
820
SnapWindow::SnapWindow (CompWindow *window) :
821
PluginClassHandler <SnapWindow, CompWindow> (window),
828
snapGeometry (0, 0, 0, 0, 0),
832
WindowInterface::setHandler (window);
835
SnapWindow::~SnapWindow ()
840
SnapPluginVTable::init ()
842
if (!CompPlugin::checkPluginABI ("core", CORE_ABIVERSION))