1 /*
2  * Compiz Fusion Grid plugin
3  *
4  * Copyright (c) 2008 Stephen Kennedy <suasol@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * Description:
17  *
18  * Plugin to act like winsplit revolution (http://www.winsplit-revolution.com/)
19  * use <Control><Alt>NUMPAD_KEY to move and tile your windows.
20  *
21  * Press the tiling keys several times to cycle through some tiling options.
22  */
23 
24 #include <compiz-core.h>
25 #include <string.h>
26 #include "grid_options.h"
27 
28 #define GRID_DEBUG 0
29 
30 #if GRID_DEBUG
31 #   include <stdio.h>
32 	static FILE* gridOut;
33 #   define DEBUG_RECT(VAR) fprintf(gridOut, #VAR " %i %i %i %i\n", VAR.x, VAR.y, VAR.width, VAR.height)
34 #   define DEBUG_PRINT(ARGS) fprintf ARGS
35 #else
36 #   define DEBUG_RECT(VAR)
37 #   define DEBUG_PRINT(ARGS)
38 #endif
39 
40 typedef enum
41 {
42     GridUnknown = 0,
43     GridBottomLeft = 1,
44     GridBottom = 2,
45     GridBottomRight = 3,
46     GridLeft = 4,
47     GridCenter = 5,
48     GridRight = 6,
49     GridTopLeft = 7,
50     GridTop = 8,
51     GridTopRight = 9,
52 } GridType;
53 
54 typedef struct _GridProps
55 {
56     int gravityRight;
57     int gravityDown;
58     int numCellsX;
59     int numCellsY;
60 } GridProps;
61 
62 static const GridProps gridProps[] =
63 {
64     {0,1, 1,1},
65 
66     {0,1, 2,2},
67     {0,1, 1,2},
68     {1,1, 2,2},
69 
70     {0,0, 2,1},
71     {0,0, 1,1},
72     {1,0, 2,1},
73 
74     {0,0, 2,2},
75     {0,0, 1,2},
76     {1,0, 2,2},
77 };
78 
79 static void
slotToRect(CompWindow * w,XRectangle * slot,XRectangle * rect)80 slotToRect (CompWindow *w,
81 	    XRectangle *slot,
82 	    XRectangle *rect)
83 {
84     rect->x = slot->x + w->input.left;
85     rect->y = slot->y + w->input.top;
86     rect->width = slot->width - (w->input.left + w->input.right);
87     rect->height = slot->height - (w->input.top + w->input.bottom);
88 }
89 
90 static void
constrainSize(CompWindow * w,XRectangle * slot,XRectangle * rect)91 constrainSize (CompWindow *w,
92 	       XRectangle *slot,
93 	       XRectangle *rect)
94 {
95     XRectangle workarea;
96     XRectangle r;
97     int        cw, ch;
98 
99     getWorkareaForOutput (w->screen, outputDeviceForWindow (w), &workarea);
100     slotToRect (w, slot, &r);
101 
102     if (constrainNewWindowSize (w, r.width, r.height, &cw, &ch))
103     {
104 	/* constrained size may put window offscreen, adjust for that case */
105 	int dx = r.x + cw - workarea.width - workarea.x + w->input.right;
106 	int dy = r.y + ch - workarea.height - workarea.y + w->input.bottom;
107 
108 	if ( dx > 0 )
109 	    r.x -= dx;
110 	if ( dy > 0 )
111 	    r.y -= dy;
112 
113 	r.width = cw;
114 	r.height = ch;
115     }
116 
117     *rect = r;
118 }
119 
120 static Bool
gridCommon(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption,GridType where)121 gridCommon (CompDisplay     *d,
122 	    CompAction      *action,
123 	    CompActionState state,
124 	    CompOption      *option,
125 	    int             nOption,
126 	    GridType        where)
127 {
128     Window     xid;
129     CompWindow *cw;
130 
131     xid = getIntOptionNamed (option, nOption, "window", 0);
132     cw  = findWindowAtDisplay (d, xid);
133     if (cw)
134     {
135 	XRectangle     workarea;
136 	XRectangle     desiredSlot;
137 	XRectangle     desiredRect;
138 	XRectangle     currentRect;
139 	GridProps      props = gridProps[where];
140 	XWindowChanges xwc;
141 
142 	DEBUG_PRINT ((gridOut, "\nPressed KP_%i\n", where));
143 
144 	/* get current available area */
145 	getWorkareaForOutput (cw->screen, outputDeviceForWindow(cw), &workarea);
146 	DEBUG_RECT (workarea);
147 
148 	/* Convention:
149 	 * xxxSlot include decorations (it's the screen area occupied)
150 	 * xxxRect are undecorated (it's the constrained position
151 	                            of the contents)
152 	 */
153 
154 	/* slice and dice to get desired slot - including decorations */
155 	desiredSlot.y =  workarea.y + props.gravityDown *
156 	                 (workarea.height / props.numCellsY);
157 	desiredSlot.height = workarea.height / props.numCellsY;
158 	desiredSlot.x =  workarea.x + props.gravityRight *
159 	                 (workarea.width / props.numCellsX);
160 	desiredSlot.width = workarea.width / props.numCellsX;
161 	DEBUG_RECT (desiredSlot);
162 
163 	/* Adjust for constraints and decorations */
164 	constrainSize (cw, &desiredSlot, &desiredRect);
165 	DEBUG_RECT (desiredRect);
166 
167 	/* Get current rect not including decorations */
168 	currentRect.x = cw->serverX;
169 	currentRect.y = cw->serverY;
170 	currentRect.width  = cw->serverWidth;
171 	currentRect.height = cw->serverHeight;
172 	DEBUG_RECT (currentRect);
173 
174 	if (desiredRect.y == currentRect.y &&
175 	    desiredRect.height == currentRect.height)
176 	{
177 	    int slotWidth33  = workarea.width / 3;
178 	    int slotWidth66  = workarea.width - slotWidth33;
179 
180 	    DEBUG_PRINT ((gridOut, "Multi!\n"));
181 
182 	    if (props.numCellsX == 2) /* keys (1, 4, 7, 3, 6, 9) */
183 	    {
184 		if (currentRect.width == desiredRect.width &&
185 		    currentRect.x == desiredRect.x)
186 		{
187 		    desiredSlot.width = slotWidth66;
188 		    desiredSlot.x = workarea.x +
189 			            props.gravityRight * slotWidth33;
190 		}
191 		else
192 		{
193 		    /* tricky, have to allow for window constraints when
194 		     * computing what the 33% and 66% offsets would be
195 		     */
196 		    XRectangle rect33, rect66, slot33, slot66;
197 
198 		    slot33 = desiredSlot;
199 		    slot33.x = workarea.x + props.gravityRight * slotWidth66;
200 		    slot33.width = slotWidth33;
201 		    constrainSize (cw, &slot33, &rect33);
202 		    DEBUG_RECT (slot33);
203 		    DEBUG_RECT (rect33);
204 
205 		    slot66 = desiredSlot;
206 		    slot66.x = workarea.x + props.gravityRight * slotWidth33;
207 		    slot66.width = slotWidth66;
208 		    constrainSize (cw, &slot66, &rect66);
209 		    DEBUG_RECT (slot66);
210 		    DEBUG_RECT (rect66);
211 
212 		    if (currentRect.width == rect66.width &&
213 			currentRect.x == rect66.x)
214 		    {
215 			desiredSlot.width = slotWidth33;
216 			desiredSlot.x = workarea.x +
217 			                props.gravityRight * slotWidth66;
218 		    }
219 		}
220 	    }
221 	    else /* keys (2, 5, 8) */
222 	    {
223 		if (currentRect.width == desiredRect.width &&
224 		    currentRect.x == desiredRect.x)
225 		{
226 		    desiredSlot.width = slotWidth33;
227 		    desiredSlot.x = workarea.x + slotWidth33;
228 		}
229 	    }
230 	    constrainSize (cw, &desiredSlot, &desiredRect);
231 	    DEBUG_RECT (desiredRect);
232 	}
233 
234 	xwc.x = desiredRect.x;
235 	xwc.y = desiredRect.y;
236 	xwc.width  = desiredRect.width;
237 	xwc.height = desiredRect.height;
238 
239 	if (cw->mapNum)
240 	    sendSyncRequest (cw);
241 
242 	if (cw->state & MAXIMIZE_STATE)
243 	{
244 	    /* maximized state interferes with us, clear it */
245 	    maximizeWindow (cw, 0);
246 	}
247 
248 	/* TODO: animate move+resize */
249 	configureXWindow (cw, CWX | CWY | CWWidth | CWHeight, &xwc);
250     }
251 
252     return TRUE;
253 }
254 
255 #define HANDLER(WHERE)                                        \
256     static Bool                                               \
257 	grid##WHERE(CompDisplay     *d,                       \
258 		    CompAction      *action,                  \
259 		    CompActionState state,                    \
260 		    CompOption      *option,                  \
261 		    int             nOption)                  \
262 	{                                                     \
263 	    return gridCommon (d, action, state,              \
264 			       option, nOption, Grid##WHERE); \
265 	}
266 
267 HANDLER (BottomLeft)
HANDLER(Bottom)268 HANDLER (Bottom)
269 HANDLER (BottomRight)
270 HANDLER (Left)
271 HANDLER (Center)
272 HANDLER (Right)
273 HANDLER (TopLeft)
274 HANDLER (Top)
275 HANDLER (TopRight)
276 
277 #undef HANDLER
278 
279 /* Configuration, initialization, boring stuff. */
280 
281 static Bool
282 gridInitDisplay (CompPlugin  *p,
283 		 CompDisplay *d)
284 {
285     if (!checkPluginABI ("core", CORE_ABIVERSION))
286 	return FALSE;
287 
288     gridSetPutCenterKeyInitiate (d, gridCenter);
289     gridSetPutLeftKeyInitiate (d, gridLeft);
290     gridSetPutRightKeyInitiate (d, gridRight);
291     gridSetPutTopKeyInitiate (d, gridTop);
292     gridSetPutBottomKeyInitiate (d, gridBottom);
293     gridSetPutTopleftKeyInitiate (d, gridTopLeft);
294     gridSetPutToprightKeyInitiate (d, gridTopRight);
295     gridSetPutBottomleftKeyInitiate (d, gridBottomLeft);
296     gridSetPutBottomrightKeyInitiate (d, gridBottomRight);
297 
298     return TRUE;
299 }
300 
301 static CompBool
gridInitObject(CompPlugin * p,CompObject * o)302 gridInitObject (CompPlugin *p,
303 		CompObject *o)
304 {
305     static InitPluginObjectProc dispTab[] = {
306 	(InitPluginObjectProc) 0, /* InitCore */
307 	(InitPluginObjectProc) gridInitDisplay
308     };
309 
310     RETURN_DISPATCH (o, dispTab, ARRAY_SIZE (dispTab), TRUE, (p, o));
311 }
312 
313 static Bool
gridInitPlugin(CompPlugin * p)314 gridInitPlugin (CompPlugin *p)
315 {
316 #if GRID_DEBUG
317     gridOut = fopen("/tmp/grid.log", "w");
318     setlinebuf(gridOut);
319 #endif
320 
321     return TRUE;
322 }
323 
324 static void
gridFiniPlugin(CompPlugin * p)325 gridFiniPlugin (CompPlugin *p)
326 {
327 #if GRID_DEBUG
328     fclose(gridOut);
329     gridOut = NULL;
330 #endif
331 }
332 
333 CompPluginVTable gridVTable =
334 {
335     "grid",
336     0,
337     gridInitPlugin,
338     gridFiniPlugin,
339     gridInitObject,
340     0,
341     0,
342     0
343 };
344 
345 CompPluginVTable *
getCompPluginInfo()346 getCompPluginInfo ()
347 {
348     return &gridVTable;
349 }
350