1 /*
2 * Copyright © 2005 Novell, Inc.
3 * Copyright (C) 2007, 2008 Kristian Lyngstøl
4 *
5 * Permission to use, copy, modify, distribute, and sell this software
6 * and its documentation for any purpose is hereby granted without
7 * fee, provided that the above copyright notice appear in all copies
8 * and that both that copyright notice and this permission notice
9 * appear in supporting documentation, and that the name of
10 * Novell, Inc. not be used in advertising or publicity pertaining to
11 * distribution of the software without specific, written prior permission.
12 * Novell, Inc. makes no representations about the suitability of this
13 * software for any purpose. It is provided "as is" without express or
14 * implied warranty.
15 *
16 * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
18 * NO EVENT SHALL NOVELL, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
20 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
21 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
22 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 *
24 *
25 * Author(s):
26 * - Original zoom plug-in; David Reveman <davidr@novell.com>
27 * - Most features beyond basic zoom;
28 * Kristian Lyngstol <kristian@bohemians.org>
29 *
30 * Description:
31 *
32 * This plug-in offers zoom functionality with focus tracking,
33 * fit-to-window actions, mouse panning, zoom area locking. Without
34 * disabling input.
35 *
36 * Note on actual zoom process
37 *
38 * The animation is done in preparePaintScreen, while instant movements
39 * are done by calling updateActualTranslate () after updating the
40 * translations. This causes [xyz]trans to be re-calculated. We keep track
41 * of each head separately.
42 *
43 * Note on input
44 *
45 * We can not redirect input yet, but this plug-in offers two fundamentally
46 * different approaches to achieve input enabled zoom:
47 *
48 * 1.
49 * Always have the zoomed area be in sync with the mouse cursor. This binds
50 * the zoom area to the mouse position at any given time. It allows using
51 * the original mouse cursor drawn by X, and is technically very safe.
52 * First used in Beryl's inputzoom.
53 *
54 * 2.
55 * Hide the real cursor and draw our own where it would be when zoomed in.
56 * This allows us to navigate with the mouse without constantly moving the
57 * zoom area. This is fairly close to what we want in the end when input
58 * redirection is available.
59 *
60 * This second method has one huge issue, which is bugged XFixes. After
61 * hiding the cursor once with XFixes, some mouse cursors will simply be
62 * invisible. The Firefox loading cursor being one of them.
63 *
64 * An other minor annoyance is that mouse sensitivity seems to increase as
65 * you zoom in, since the mouse isn't really zoomed at all.
66 *
67 * Todo:
68 * - Different multi head modes
69 */
70
71 #include <stdio.h>
72 #include <stdlib.h>
73 #include <string.h>
74 #include <math.h>
75 #include <sys/time.h>
76 #include <time.h>
77
78 #include <compiz-core.h>
79 #include <compiz-mousepoll.h>
80
81 static CompMetadata zoomMetadata;
82
83 static int displayPrivateIndex;
84
85 typedef enum _ZdOpt
86 {
87 DOPT_INITIATE = 0,
88 DOPT_IN,
89 DOPT_OUT,
90 DOPT_IN_KEY,
91 DOPT_OUT_KEY,
92 DOPT_SPECIFIC_1,
93 DOPT_SPECIFIC_2,
94 DOPT_SPECIFIC_3,
95 DOPT_SPECIFIC_LEVEL_1,
96 DOPT_SPECIFIC_LEVEL_2,
97 DOPT_SPECIFIC_LEVEL_3,
98 DOPT_SPECIFIC_TARGET_FOCUS,
99 DOPT_PAN_LEFT,
100 DOPT_PAN_RIGHT,
101 DOPT_PAN_UP,
102 DOPT_PAN_DOWN,
103 DOPT_FIT_TO_WINDOW,
104 DOPT_CENTER_MOUSE,
105 DOPT_FIT_TO_ZOOM,
106 DOPT_ENSURE_VISIBILITY,
107 DOPT_SET_ZOOM_AREA,
108 DOPT_LOCK_ZOOM,
109 DOPT_ZOOM_BOX,
110 DOPT_NUM
111 } ZoomDisplayOptions;
112
113 typedef enum _ZsOpt
114 {
115 SOPT_FOLLOW_FOCUS = 0,
116 SOPT_SPEED,
117 SOPT_TIMESTEP,
118 SOPT_ZOOM_FACTOR,
119 SOPT_FILTER_LINEAR,
120 SOPT_SYNC_MOUSE,
121 SOPT_FOCUS_DELAY,
122 SOPT_PAN_FACTOR,
123 SOPT_FOCUS_FIT_WINDOW,
124 SOPT_ALLWAYS_FOCUS_FIT_WINDOW,
125 SOPT_SCALE_MOUSE,
126 SOPT_SCALE_MOUSE_DYNAMIC,
127 SOPT_SCALE_MOUSE_STATIC,
128 SOPT_HIDE_ORIGINAL_MOUSE,
129 SOPT_RESTRAIN_MOUSE,
130 SOPT_RESTRAIN_MARGIN,
131 SOPT_MOUSE_PAN,
132 SOPT_MINIMUM_ZOOM,
133 SOPT_AUTOSCALE_MIN,
134 SOPT_NUM
135 } ZoomScreenOptions;
136
137 typedef enum {
138 NORTHEAST,
139 NORTHWEST,
140 SOUTHEAST,
141 SOUTHWEST,
142 CENTER
143 } ZoomGravity;
144
145 typedef enum {
146 NORTH,
147 SOUTH,
148 EAST,
149 WEST
150 } ZoomEdge;
151
152 typedef struct _CursorTexture
153 {
154 Bool isSet;
155 GLuint texture;
156 CompScreen *screen;
157 int width;
158 int height;
159 int hotX;
160 int hotY;
161 } CursorTexture;
162
163 typedef struct _ZoomDisplay {
164 HandleEventProc handleEvent;
165 MousePollFunc *mpFunc;
166
167 int screenPrivateIndex;
168 Bool fixesSupported;
169 int fixesEventBase;
170 int fixesErrorBase;
171 Bool canHideCursor;
172 CompOption opt[DOPT_NUM];
173 } ZoomDisplay;
174
175 /* Stores an actual zoom-setup. This can later be used to store/restore
176 * zoom areas on the fly.
177 *
178 * [xy]Translate and newZoom are target values, and [xy]Translate always
179 * ranges from -0.5 to 0.5.
180 *
181 * currentZoom is actual zoomed value
182 *
183 * real[XY]Translate are the currently used values in the same range as
184 * [xy]Translate, and [xy]trans is adjusted for the zoom level in place.
185 * [xyz]trans should never be modified except in updateActualTranslates()
186 *
187 * viewport is a mask of the viewport, or ~0 for "any".
188 */
189 typedef struct _ZoomArea {
190 int output;
191 unsigned long int viewport;
192 GLfloat currentZoom;
193 GLfloat newZoom;
194 GLfloat xVelocity;
195 GLfloat yVelocity;
196 GLfloat zVelocity;
197 GLfloat xTranslate;
198 GLfloat yTranslate;
199 GLfloat realXTranslate;
200 GLfloat realYTranslate;
201 GLfloat xtrans;
202 GLfloat ytrans;
203 Bool locked;
204 } ZoomArea;
205
206 typedef struct _ZoomScreen {
207 PreparePaintScreenProc preparePaintScreen;
208 DonePaintScreenProc donePaintScreen;
209 PaintOutputProc paintOutput;
210 PositionPollingHandle pollHandle;
211 CompOption opt[SOPT_NUM];
212 ZoomArea *zooms;
213 int nZooms;
214 int mouseX;
215 int mouseY;
216 unsigned long int grabbed;
217 int grabIndex; // for zoomBox
218 time_t lastChange;
219 CursorTexture cursor;
220 Bool cursorInfoSelected;
221 Bool cursorHidden;
222 Box box;
223 } ZoomScreen;
224
225 static void syncCenterToMouse (CompScreen *s);
226 static void updateMouseInterval (CompScreen *s, int x, int y);
227 static void cursorZoomActive (CompScreen *s);
228 static void cursorZoomInactive (CompScreen *s);
229 static void restrainCursor (CompScreen *s, int out);
230
231 static void drawCursor (CompScreen *s,
232 CompOutput *output,
233 const CompTransform *transform);
234 static void convertToZoomedTarget (CompScreen *s,
235 int out,
236 int x,
237 int y,
238 int *resultX,
239 int *resultY);
240 static void
241 convertToZoomed (CompScreen *s,
242 int out,
243 int x,
244 int y,
245 int *resultX,
246 int *resultY);
247
248 #define GET_ZOOM_DISPLAY(d) \
249 ((ZoomDisplay *) (d)->base.privates[displayPrivateIndex].ptr)
250
251 #define ZOOM_DISPLAY(d) \
252 ZoomDisplay *zd = GET_ZOOM_DISPLAY (d)
253
254 #define GET_ZOOM_SCREEN(s, zd) \
255 ((ZoomScreen *) (s)->base.privates[(zd)->screenPrivateIndex].ptr)
256
257 #define ZOOM_SCREEN(s) \
258 ZoomScreen *zs = GET_ZOOM_SCREEN (s, GET_ZOOM_DISPLAY (s->display))
259
260 #define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))
261
262 /* Checks if a specific screen grab exist. DO NOT USE THIS.
263 * This is a temporary fix that SHOULD be removed asap.
264 * See comments in drawCursor.
265 */
266
267 static inline Bool
dontuseScreengrabExist(CompScreen * s,char * grab)268 dontuseScreengrabExist (CompScreen * s, char * grab)
269 {
270 int i;
271 for (i = 0; i < s->maxGrab; i++)
272 if (s->grabs[i].active && !strcmp(s->grabs[i].name, grab))
273 return TRUE;
274 return FALSE;
275 }
276
277 /* Check if the output is valid */
278 static inline Bool
outputIsZoomArea(CompScreen * s,int out)279 outputIsZoomArea (CompScreen *s, int out)
280 {
281 ZOOM_SCREEN (s);
282
283 if (out < 0 || out >= zs->nZooms)
284 return FALSE;
285 return TRUE;
286 }
287
288 /* Check if zoom is active on the output specified */
289 static inline Bool
isActive(CompScreen * s,int out)290 isActive (CompScreen *s, int out)
291 {
292 ZOOM_SCREEN (s);
293
294 if (!outputIsZoomArea (s, out))
295 return FALSE;
296 if (zs->grabbed & (1 << zs->zooms[out].output))
297 return TRUE;
298 return FALSE;
299 }
300
301 /* Check if we are zoomed out and not going anywhere
302 * (similar to isActive but based on actual zoom, not grab)
303 */
304 static inline Bool
isZoomed(CompScreen * s,int out)305 isZoomed (CompScreen *s, int out)
306 {
307 ZOOM_SCREEN (s);
308
309 if (!outputIsZoomArea (s, out))
310 return FALSE;
311
312 if (zs->zooms[out].currentZoom != 1.0f
313 || zs->zooms[out].newZoom != 1.0f)
314 return TRUE;
315
316 if (zs->zooms[out].zVelocity != 0.0f)
317 return TRUE;
318
319 return FALSE;
320 }
321
322 /* Returns the distance to the defined edge in zoomed pixels. */
323 static int
distanceToEdge(CompScreen * s,int out,ZoomEdge edge)324 distanceToEdge (CompScreen *s, int out, ZoomEdge edge)
325 {
326 int x1,y1,x2,y2;
327 CompOutput *o = &s->outputDev[out];
328
329 if (!isActive (s, out))
330 return 0;
331 convertToZoomedTarget (s, out, o->region.extents.x2,
332 o->region.extents.y2, &x2, &y2);
333 convertToZoomedTarget (s, out, o->region.extents.x1,
334 o->region.extents.y1, &x1, &y1);
335 switch (edge)
336 {
337 case NORTH: return o->region.extents.y1 - y1;
338 case SOUTH: return y2 - o->region.extents.y2;
339 case EAST: return x2 - o->region.extents.x2;
340 case WEST: return o->region.extents.x1 - x1;
341 }
342 return 0; // Never reached.
343 }
344
345 /* Update/set translations based on zoom level and real translate. */
346 static void
updateActualTranslates(ZoomArea * za)347 updateActualTranslates (ZoomArea *za)
348 {
349 za->xtrans = -za->realXTranslate * (1.0f - za->currentZoom);
350 za->ytrans = za->realYTranslate * (1.0f - za->currentZoom);
351 }
352
353 /* Returns true if the head in question is currently moving.
354 * Since we don't always bother resetting everything when
355 * canceling zoom, we check for the condition of being completely
356 * zoomed out and not zooming in/out first.
357 */
358 static Bool
isInMovement(CompScreen * s,int out)359 isInMovement (CompScreen *s, int out)
360 {
361 ZOOM_SCREEN (s);
362
363 if (zs->zooms[out].currentZoom == 1.0f &&
364 zs->zooms[out].newZoom == 1.0f &&
365 zs->zooms[out].zVelocity == 0.0f)
366 return FALSE;
367 if (zs->zooms[out].currentZoom != zs->zooms[out].newZoom ||
368 zs->zooms[out].xVelocity || zs->zooms[out].yVelocity ||
369 zs->zooms[out].zVelocity)
370 return TRUE;
371 if (zs->zooms[out].xTranslate != zs->zooms[out].realXTranslate ||
372 zs->zooms[out].yTranslate != zs->zooms[out].realYTranslate)
373 return TRUE;
374 return FALSE;
375 }
376
377 /* Set the initial values of a zoom area. */
378 static void
initialiseZoomArea(ZoomArea * za,int out)379 initialiseZoomArea (ZoomArea *za, int out)
380 {
381 za->output = out;
382 za->currentZoom = 1.0f;
383 za->newZoom = 1.0f;
384 za->xVelocity = 0.0f;
385 za->yVelocity = 0.0f;
386 za->zVelocity = 0.0f;
387 za->xTranslate = 0.0f;
388 za->yTranslate = 0.0f;
389 za->realXTranslate = 0.0f;
390 za->realYTranslate = 0.0f;
391 za->viewport = ~0;
392 za->locked = FALSE;
393 updateActualTranslates (za);
394 }
395
396 /* Adjust the velocity in the z-direction. */
397 static void
adjustZoomVelocity(CompScreen * s,int out,float chunk)398 adjustZoomVelocity (CompScreen *s, int out, float chunk)
399 {
400 float d, adjust, amount;
401 ZOOM_SCREEN (s);
402
403 d = (zs->zooms[out].newZoom - zs->zooms[out].currentZoom) * 75.0f;
404
405 adjust = d * 0.002f;
406 amount = fabs (d);
407 if (amount < 1.0f)
408 amount = 1.0f;
409 else if (amount > 5.0f)
410 amount = 5.0f;
411
412 zs->zooms[out].zVelocity =
413 (amount * zs->zooms[out].zVelocity + adjust) / (amount + 1.0f);
414
415 if (fabs (d) < 0.1f && fabs (zs->zooms[out].zVelocity) < 0.005f)
416 {
417 zs->zooms[out].currentZoom = zs->zooms[out].newZoom;
418 zs->zooms[out].zVelocity = 0.0f;
419 }
420 else
421 {
422 zs->zooms[out].currentZoom += (zs->zooms[out].zVelocity * chunk) /
423 s->redrawTime;
424 }
425 }
426
427 /* Adjust the X/Y velocity based on target translation and real
428 * translation. */
429 static void
adjustXYVelocity(CompScreen * s,int out,float chunk)430 adjustXYVelocity (CompScreen *s, int out, float chunk)
431 {
432 float xdiff, ydiff;
433 float xadjust, yadjust;
434 float xamount, yamount;
435 ZOOM_SCREEN (s);
436
437 zs->zooms[out].xVelocity /= 1.25f;
438 zs->zooms[out].yVelocity /= 1.25f;
439 xdiff =
440 (zs->zooms[out].xTranslate - zs->zooms[out].realXTranslate) *
441 75.0f;
442 ydiff =
443 (zs->zooms[out].yTranslate - zs->zooms[out].realYTranslate) *
444 75.0f;
445 xadjust = xdiff * 0.002f;
446 yadjust = ydiff * 0.002f;
447 xamount = fabs (xdiff);
448 yamount = fabs (ydiff);
449
450 if (xamount < 1.0f)
451 xamount = 1.0f;
452 else if (xamount > 5.0)
453 xamount = 5.0f;
454
455 if (yamount < 1.0f)
456 yamount = 1.0f;
457 else if (yamount > 5.0)
458 yamount = 5.0f;
459
460 zs->zooms[out].xVelocity =
461 (xamount * zs->zooms[out].xVelocity + xadjust) / (xamount + 1.0f);
462 zs->zooms[out].yVelocity =
463 (yamount * zs->zooms[out].yVelocity + yadjust) / (yamount + 1.0f);
464
465 if ((fabs(xdiff) < 0.1f && fabs (zs->zooms[out].xVelocity) < 0.005f) &&
466 (fabs(ydiff) < 0.1f && fabs (zs->zooms[out].yVelocity) < 0.005f))
467 {
468 zs->zooms[out].realXTranslate = zs->zooms[out].xTranslate;
469 zs->zooms[out].realYTranslate = zs->zooms[out].yTranslate;
470 zs->zooms[out].xVelocity = 0.0f;
471 zs->zooms[out].yVelocity = 0.0f;
472 return;
473 }
474
475 zs->zooms[out].realXTranslate +=
476 (zs->zooms[out].xVelocity * chunk) / s->redrawTime;
477 zs->zooms[out].realYTranslate +=
478 (zs->zooms[out].yVelocity * chunk) / s->redrawTime;
479 }
480
481 /* Animate the movement (if any) in preparation of a paint screen. */
482 static void
zoomPreparePaintScreen(CompScreen * s,int msSinceLastPaint)483 zoomPreparePaintScreen (CompScreen *s,
484 int msSinceLastPaint)
485 {
486 ZOOM_SCREEN (s);
487
488 if (zs->grabbed)
489 {
490 int steps;
491 float amount, chunk;
492
493 amount = msSinceLastPaint * 0.05f * zs->opt[SOPT_SPEED].value.f;
494 steps = amount / (0.5f * zs->opt[SOPT_TIMESTEP].value.f);
495 if (!steps)
496 steps = 1;
497 chunk = amount / (float) steps;
498 while (steps--)
499 {
500 int out;
501 for (out = 0; out < zs->nZooms; out++)
502 {
503 if (!isInMovement (s, out) || !isActive (s, out))
504 continue;
505
506 adjustXYVelocity (s, out, chunk);
507 adjustZoomVelocity (s, out, chunk);
508 updateActualTranslates (&zs->zooms[out]);
509 if (!isZoomed (s, out))
510 {
511 zs->zooms[out].xVelocity = zs->zooms[out].yVelocity =
512 0.0f;
513 zs->grabbed &= ~(1 << zs->zooms[out].output);
514 }
515 }
516 }
517 if (zs->opt[SOPT_SYNC_MOUSE].value.b)
518 syncCenterToMouse (s);
519 }
520 UNWRAP (zs, s, preparePaintScreen);
521 (*s->preparePaintScreen) (s, msSinceLastPaint);
522 WRAP (zs, s, preparePaintScreen, zoomPreparePaintScreen);
523 }
524
525 /* Damage screen if we're still moving. */
526 static void
zoomDonePaintScreen(CompScreen * s)527 zoomDonePaintScreen (CompScreen *s)
528 {
529 ZOOM_SCREEN (s);
530
531 if (zs->grabbed)
532 {
533 int out;
534 for (out = 0; out < zs->nZooms; out++)
535 {
536 if (isInMovement (s, out) && isActive (s,out))
537 {
538 damageScreen (s);
539 break;
540 }
541 }
542 }
543 UNWRAP (zs, s, donePaintScreen);
544 (*s->donePaintScreen) (s);
545 WRAP (zs, s, donePaintScreen, zoomDonePaintScreen);
546 }
547 /* Draws a box from the screen coordinates inx1,iny1 to inx2,iny2 */
548 static void
drawBox(CompScreen * s,const CompTransform * transform,CompOutput * output,Box box)549 drawBox (CompScreen *s,
550 const CompTransform *transform,
551 CompOutput *output,
552 Box box)
553 {
554 CompTransform zTransform = *transform;
555 int x1,x2,y1,y2;
556 int inx1, inx2, iny1, iny2;
557 int out = output->id;
558
559 transformToScreenSpace (s, output, -DEFAULT_Z_CAMERA, &zTransform);
560 convertToZoomed (s, out, box.x1, box.y1, &inx1, &iny1);
561 convertToZoomed (s, out, box.x2, box.y2, &inx2, &iny2);
562
563 x1 = MIN (inx1, inx2);
564 y1 = MIN (iny1, iny2);
565 x2 = MAX (inx1, inx2);
566 y2 = MAX (iny1, iny2);
567 glPushMatrix ();
568 glLoadMatrixf (zTransform.m);
569 glDisableClientState (GL_TEXTURE_COORD_ARRAY);
570 glEnable (GL_BLEND);
571 glColor4us (0x2fff, 0x2fff, 0x4fff, 0x4fff);
572 glRecti (x1,y2,x2,y1);
573 glColor4us (0x2fff, 0x2fff, 0x4fff, 0x9fff);
574 glBegin (GL_LINE_LOOP);
575 glVertex2i (x1, y1);
576 glVertex2i (x2, y1);
577 glVertex2i (x2, y2);
578 glVertex2i (x1, y2);
579 glEnd ();
580 glColor4usv (defaultColor);
581 glDisable (GL_BLEND);
582 glEnableClientState (GL_TEXTURE_COORD_ARRAY);
583 glPopMatrix ();
584 }
585 /* Apply the zoom if we are grabbed.
586 * Make sure to use the correct filter.
587 */
588 static Bool
zoomPaintOutput(CompScreen * s,const ScreenPaintAttrib * sAttrib,const CompTransform * transform,Region region,CompOutput * output,unsigned int mask)589 zoomPaintOutput (CompScreen *s,
590 const ScreenPaintAttrib *sAttrib,
591 const CompTransform *transform,
592 Region region,
593 CompOutput *output,
594 unsigned int mask)
595 {
596 Bool status;
597 int out = output->id;
598 ZOOM_SCREEN (s);
599
600
601 if (isActive (s, out))
602 {
603 ScreenPaintAttrib sa = *sAttrib;
604 int saveFilter;
605 CompTransform zTransform = *transform;
606
607 mask &= ~PAINT_SCREEN_REGION_MASK;
608 mask |= PAINT_SCREEN_CLEAR_MASK;
609
610 matrixScale (&zTransform,
611 1.0f / zs->zooms[out].currentZoom,
612 1.0f / zs->zooms[out].currentZoom,
613 1.0f);
614 matrixTranslate (&zTransform,
615 zs->zooms[out].xtrans,
616 zs->zooms[out].ytrans,
617 0);
618
619 mask |= PAINT_SCREEN_TRANSFORMED_MASK;
620 saveFilter = s->filter[SCREEN_TRANS_FILTER];
621
622 if (zs->opt[SOPT_FILTER_LINEAR].value.b)
623 s->filter[SCREEN_TRANS_FILTER] = COMP_TEXTURE_FILTER_GOOD;
624 else
625 s->filter[SCREEN_TRANS_FILTER] = COMP_TEXTURE_FILTER_FAST;
626
627 UNWRAP (zs, s, paintOutput);
628 status =
629 (*s->paintOutput) (s, &sa, &zTransform, region, output, mask);
630 WRAP (zs, s, paintOutput, zoomPaintOutput);
631 drawCursor (s, output, transform);
632
633 s->filter[SCREEN_TRANS_FILTER] = saveFilter;
634 }
635 else
636 {
637 UNWRAP (zs, s, paintOutput);
638 status = (*s->paintOutput) (s,
639 sAttrib,
640 transform,
641 region,
642 output,
643 mask);
644 WRAP (zs, s, paintOutput, zoomPaintOutput);
645 }
646 if (zs->grabIndex)
647 drawBox (s, transform, output, zs->box);
648
649 return status;
650 }
651
652 /* Makes sure we're not attempting to translate too far.
653 * We are restricted to 0.5 to not go beyond the end
654 * of the screen/head. */
655 static inline void
constrainZoomTranslate(CompScreen * s)656 constrainZoomTranslate (CompScreen *s)
657 {
658 int out;
659 ZOOM_SCREEN (s);
660
661 for (out = 0; out < zs->nZooms; out++)
662 {
663 if (zs->zooms[out].xTranslate > 0.5f)
664 zs->zooms[out].xTranslate = 0.5f;
665 else if (zs->zooms[out].xTranslate < -0.5f)
666 zs->zooms[out].xTranslate = -0.5f;
667
668 if (zs->zooms[out].yTranslate > 0.5f)
669 zs->zooms[out].yTranslate = 0.5f;
670 else if (zs->zooms[out].yTranslate < -0.5f)
671 zs->zooms[out].yTranslate = -0.5f;
672 }
673 }
674
675 /* Functions for adjusting the zoomed area.
676 * These are the core of the zoom plug-in; Anything wanting
677 * to adjust the zoomed area must use setCenter or setZoomArea
678 * and setScale or front ends to them. */
679
680 /* Sets the center of the zoom area to X,Y.
681 * We have to be able to warp the pointer here: If we are moved by
682 * anything except mouse movement, we have to sync the
683 * mouse pointer. This is to allow input, and is NOT necessary
684 * when input redirection is available to us or if we're cheating
685 * and using a scaled mouse cursor to imitate IR.
686 * The center is not the center of the screen. This is the target-center;
687 * that is, it's the point that's the same regardless of zoom level.
688 */
689 static void
setCenter(CompScreen * s,int x,int y,Bool instant)690 setCenter (CompScreen *s, int x, int y, Bool instant)
691 {
692 int out = outputDeviceForPoint (s, x,y);
693 CompOutput *o = &s->outputDev[out];
694 ZOOM_SCREEN (s);
695
696 if (zs->zooms[out].locked)
697 return;
698
699 zs->zooms[out].xTranslate = (float)
700 ((x - o->region.extents.x1) - o->width / 2) / (o->width);
701 zs->zooms[out].yTranslate = (float)
702 ((y - o->region.extents.y1) - o->height / 2) / (o->height);
703
704 if (instant)
705 {
706 zs->zooms[out].realXTranslate = zs->zooms[out].xTranslate;
707 zs->zooms[out].realYTranslate = zs->zooms[out].yTranslate;
708 zs->zooms[out].yVelocity = 0.0f;
709 zs->zooms[out].xVelocity = 0.0f;
710 updateActualTranslates (&zs->zooms[out]);
711 }
712
713 if (zs->opt[SOPT_MOUSE_PAN].value.b)
714 restrainCursor (s, out);
715 }
716
717 /* Zooms the area described.
718 * The math could probably be cleaned up, but should be correct now. */
719 static void
setZoomArea(CompScreen * s,int x,int y,int width,int height,Bool instant)720 setZoomArea (CompScreen *s,
721 int x,
722 int y,
723 int width,
724 int height,
725 Bool instant)
726 {
727 int out = outputDeviceForGeometry (s, x, y, width, height, 0);
728 CompOutput *o = &s->outputDev[out];
729 ZOOM_SCREEN (s);
730
731 if (zs->zooms[out].newZoom == 1.0f)
732 return;
733
734 if (zs->zooms[out].locked)
735 return;
736 zs->zooms[out].xTranslate =
737 (float) -((o->width/2) - (x + (width/2) - o->region.extents.x1))
738 / (o->width);
739 zs->zooms[out].xTranslate /= (1.0f - zs->zooms[out].newZoom);
740 zs->zooms[out].yTranslate =
741 (float) -((o->height/2) - (y + (height/2) - o->region.extents.y1))
742 / (o->height);
743 zs->zooms[out].yTranslate /= (1.0f - zs->zooms[out].newZoom);
744 constrainZoomTranslate (s);
745
746 if (instant)
747 {
748 zs->zooms[out].realXTranslate = zs->zooms[out].xTranslate;
749 zs->zooms[out].realYTranslate = zs->zooms[out].yTranslate;
750 updateActualTranslates (&zs->zooms[out]);
751 }
752
753 if (zs->opt[SOPT_MOUSE_PAN].value.b)
754 restrainCursor (s, out);
755 }
756
757 /* Moves the zoom area to the window specified */
758 static void
zoomAreaToWindow(CompWindow * w)759 zoomAreaToWindow (CompWindow *w)
760 {
761 int left = w->serverX - w->input.left;
762 int width = w->width + w->input.left + w->input.right;
763 int top = w->serverY - w->input.top;
764 int height = w->height + w->input.top + w->input.bottom;
765
766 setZoomArea (w->screen, left, top, width, height, FALSE);
767 }
768
769 /* Pans the zoomed area vertically/horizontally by * value * zs->panFactor
770 * TODO: Fix output. */
771 static void
panZoom(CompScreen * s,int xvalue,int yvalue)772 panZoom (CompScreen *s, int xvalue, int yvalue)
773 {
774 int out;
775 ZOOM_SCREEN (s);
776
777 for (out = 0; out < zs->nZooms; out++)
778 {
779 zs->zooms[out].xTranslate +=
780 zs->opt[SOPT_PAN_FACTOR].value.f * xvalue *
781 zs->zooms[out].currentZoom;
782 zs->zooms[out].yTranslate +=
783 zs->opt[SOPT_PAN_FACTOR].value.f * yvalue *
784 zs->zooms[out].currentZoom;
785 }
786
787 constrainZoomTranslate (s);
788 }
789
790 /* Enables polling of mouse position, and refreshes currently
791 * stored values.
792 */
793 static void
enableMousePolling(CompScreen * s)794 enableMousePolling (CompScreen *s)
795 {
796 ZOOM_SCREEN (s);
797 ZOOM_DISPLAY (s->display);
798 zs->pollHandle =
799 (*zd->mpFunc->addPositionPolling) (s, updateMouseInterval);
800 zs->lastChange = time(NULL);
801 (*zd->mpFunc->getCurrentPosition) (s, &zs->mouseX, &zs->mouseY);
802 }
803
804 /* Sets the zoom (or scale) level.
805 * Cleans up if we are suddenly zoomed out.
806 */
807 static void
setScale(CompScreen * s,int out,float value)808 setScale (CompScreen *s, int out, float value)
809 {
810 ZOOM_SCREEN(s);
811
812 if (zs->zooms[out].locked)
813 return;
814
815 if (value >= 1.0f)
816 value = 1.0f;
817 else
818 {
819 if (!zs->pollHandle)
820 enableMousePolling (s);
821 zs->grabbed |= (1 << zs->zooms[out].output);
822 cursorZoomActive (s);
823 }
824
825 if (value == 1.0f)
826 {
827 zs->zooms[out].xTranslate = 0.0f;
828 zs->zooms[out].yTranslate = 0.0f;
829 cursorZoomInactive (s);
830 }
831
832 if (value < zs->opt[SOPT_MINIMUM_ZOOM].value.f)
833 value = zs->opt[SOPT_MINIMUM_ZOOM].value.f;
834
835 zs->zooms[out].newZoom = value;
836 damageScreen(s);
837 }
838
839 /* Sets the zoom factor to the bigger of the two floats supplied.
840 * Convenience function for setting the scale factor for an area.
841 */
842 static inline void
setScaleBigger(CompScreen * s,int out,float x,float y)843 setScaleBigger (CompScreen *s, int out, float x, float y)
844 {
845 setScale (s, out, x > y ? x : y);
846 }
847
848 /* Mouse code...
849 * This takes care of keeping the mouse in sync with the zoomed area and
850 * vice versa.
851 * See heading for description.
852 */
853
854 /* Syncs the center, based on translations, back to the mouse.
855 * This should be called when doing non-IR zooming and moving the zoom
856 * area based on events other than mouse movement.
857 */
858 static void
syncCenterToMouse(CompScreen * s)859 syncCenterToMouse (CompScreen *s)
860 {
861 int x, y;
862 int out;
863 CompOutput *o;
864 ZOOM_SCREEN (s);
865
866 out = outputDeviceForPoint (s, zs->mouseX, zs->mouseY);
867 o = &s->outputDev[out];
868
869 if (!isInMovement (s, out))
870 return;
871
872 x = (int) ((zs->zooms[out].realXTranslate * s->width) +
873 (o->width / 2) + o->region.extents.x1);
874 y = (int) ((zs->zooms[out].realYTranslate * s->height) +
875 (o->height / 2) + o->region.extents.y1);
876
877 if ((x != zs->mouseX || y != zs->mouseY)
878 && zs->grabbed && zs->zooms[out].newZoom != 1.0f)
879 {
880 warpPointer (s, x - pointerX , y - pointerY );
881 zs->mouseX = x;
882 zs->mouseY = y;
883 }
884 }
885
886 /* Convert the point X,Y to where it would be when zoomed. */
887 static void
convertToZoomed(CompScreen * s,int out,int x,int y,int * resultX,int * resultY)888 convertToZoomed (CompScreen *s,
889 int out,
890 int x,
891 int y,
892 int *resultX,
893 int *resultY)
894 {
895 CompOutput *o = &s->outputDev[out];
896 ZoomArea *za;
897 ZOOM_SCREEN (s);
898
899 za = &zs->zooms[out];
900 x -= o->region.extents.x1;
901 y -= o->region.extents.y1;
902 *resultX = x - (za->realXTranslate *
903 (1.0f - za->currentZoom) * o->width) - o->width/2;
904 *resultX /= za->currentZoom;
905 *resultX += o->width/2;
906 *resultX += o->region.extents.x1;
907 *resultY = y - (za->realYTranslate *
908 (1.0f - za->currentZoom) * o->height) - o->height/2;
909 *resultY /= za->currentZoom;
910 *resultY += o->height/2;
911 *resultY += o->region.extents.y1;
912 }
913
914 /* Same but use targeted translation, not real */
915 static void
convertToZoomedTarget(CompScreen * s,int out,int x,int y,int * resultX,int * resultY)916 convertToZoomedTarget (CompScreen *s,
917 int out,
918 int x,
919 int y,
920 int *resultX,
921 int *resultY)
922 {
923 CompOutput *o = &s->outputDev[out];
924 ZoomArea *za;
925 ZOOM_SCREEN (s);
926
927 za = &zs->zooms[out];
928 x -= o->region.extents.x1;
929 y -= o->region.extents.y1;
930 *resultX = x - (za->xTranslate *
931 (1.0f - za->newZoom) * o->width) - o->width/2;
932 *resultX /= za->newZoom;
933 *resultX += o->width/2;
934 *resultX += o->region.extents.x1;
935 *resultY = y - (za->yTranslate *
936 (1.0f - za->newZoom) * o->height) - o->height/2;
937 *resultY /= za->newZoom;
938 *resultY += o->height/2;
939 *resultY += o->region.extents.y1;
940 }
941
942 /* Make sure the given point + margin is visible;
943 * Translate to make it visible if necessary.
944 * Returns false if the point isn't on a actively zoomed head
945 * or the area is locked. */
946 static Bool
ensureVisibility(CompScreen * s,int x,int y,int margin)947 ensureVisibility (CompScreen *s, int x, int y, int margin)
948 {
949 int zoomX, zoomY;
950 int out;
951 CompOutput *o;
952 ZOOM_SCREEN (s);
953
954 out = outputDeviceForPoint (s, x, y);
955 if (!isActive (s, out))
956 return FALSE;
957
958 o = &s->outputDev[out];
959 convertToZoomedTarget (s, out, x, y, &zoomX, &zoomY);
960 ZoomArea *za = &zs->zooms[out];
961 if (za->locked)
962 return FALSE;
963
964 #define FACTOR (za->newZoom / (1.0f - za->newZoom))
965 if (zoomX + margin > o->region.extents.x2)
966 za->xTranslate +=
967 (FACTOR * (float) (zoomX + margin - o->region.extents.x2)) /
968 (float) o->width;
969 else if (zoomX - margin < o->region.extents.x1)
970 za->xTranslate +=
971 (FACTOR * (float) (zoomX - margin - o->region.extents.x1)) /
972 (float) o->width;
973
974 if (zoomY + margin > o->region.extents.y2)
975 za->yTranslate +=
976 (FACTOR * (float) (zoomY + margin - o->region.extents.y2)) /
977 (float) o->height;
978 else if (zoomY - margin < o->region.extents.y1)
979 za->yTranslate +=
980 (FACTOR * (float) (zoomY - margin - o->region.extents.y1)) /
981 (float) o->height;
982 #undef FACTOR
983 constrainZoomTranslate (s);
984 return TRUE;
985 }
986
987 /* Attempt to ensure the visibility of an area defined by x1/y1 and x2/y2.
988 * See ensureVisibility () for details.
989 *
990 * This attempts to find the translations that leaves the biggest part of
991 * the area visible.
992 *
993 * gravity defines what part of the window that should get
994 * priority if it isn't possible to fit all of it.
995 */
996 static void
ensureVisibilityArea(CompScreen * s,int x1,int y1,int x2,int y2,int margin,ZoomGravity gravity)997 ensureVisibilityArea (CompScreen *s,
998 int x1,
999 int y1,
1000 int x2,
1001 int y2,
1002 int margin,
1003 ZoomGravity gravity)
1004 {
1005 int targetX, targetY, targetW, targetH;
1006 int out;
1007 CompOutput *o;
1008 ZOOM_SCREEN (s);
1009
1010 out = outputDeviceForPoint (s, x1 + (x2-x1/2), y1 + (y2-y1/2));
1011 o = &s->outputDev[out];
1012
1013 #define WIDTHOK (float)(x2-x1) / (float)o->width < zs->zooms[out].newZoom
1014 #define HEIGHTOK (float)(y2-y1) / (float)o->height < zs->zooms[out].newZoom
1015
1016 if (WIDTHOK &&
1017 HEIGHTOK) {
1018 ensureVisibility (s, x1, y1, margin);
1019 ensureVisibility (s, x2, y2, margin);
1020 return;
1021 }
1022
1023 switch (gravity)
1024 {
1025 case NORTHWEST:
1026 targetX = x1;
1027 targetY = y1;
1028 if (WIDTHOK)
1029 targetW = x2 - x1;
1030 else
1031 targetW = o->width * zs->zooms[out].newZoom;
1032 if (HEIGHTOK)
1033 targetH = y2 - y1;
1034 else
1035 targetH = o->height * zs->zooms[out].newZoom;
1036 break;
1037 case NORTHEAST:
1038 targetY = y1;
1039 if (WIDTHOK)
1040 {
1041 targetX = x1;
1042 targetW = x2-x1;
1043 }
1044 else
1045 {
1046 targetX = x2 - o->width * zs->zooms[out].newZoom;
1047 targetW = o->width * zs->zooms[out].newZoom;
1048 }
1049
1050 if (HEIGHTOK)
1051 targetH = y2-y1;
1052 else
1053 targetH = o->height * zs->zooms[out].newZoom;
1054 break;
1055 case SOUTHWEST:
1056 targetX = x1;
1057 if (WIDTHOK)
1058 targetW = x2-x1;
1059 else
1060 targetW = o->width * zs->zooms[out].newZoom;
1061 if (HEIGHTOK)
1062 {
1063 targetY = y1;
1064 targetH = y2-y1;
1065 }
1066 else
1067 {
1068 targetY = y2 - (o->width * zs->zooms[out].newZoom);
1069 targetH = o->width * zs->zooms[out].newZoom;
1070 }
1071 break;
1072 case SOUTHEAST:
1073 if (WIDTHOK)
1074 {
1075 targetX = x1;
1076 targetW = x2-x1;
1077 }
1078 else
1079 {
1080 targetW = o->width * zs->zooms[out].newZoom;
1081 targetX = x2 - targetW;
1082 }
1083
1084 if (HEIGHTOK)
1085 {
1086 targetY = y1;
1087 targetH = y2 - y1;
1088 }
1089 else
1090 {
1091 targetH = o->height * zs->zooms[out].newZoom;
1092 targetY = y2 - targetH;
1093 }
1094 break;
1095 case CENTER:
1096 setCenter (s, x1 + (x2 - x1 / 2), y1 + (y2 - y1 / 2), FALSE);
1097 return;
1098 break;
1099 }
1100
1101 setZoomArea (s, targetX, targetY, targetW, targetH, FALSE);
1102 return ;
1103 }
1104
1105 /* Ensures that the cursor is visible on the given head.
1106 * Note that we check if currentZoom is 1.0f, because that often means that
1107 * mouseX and mouseY is not up-to-date (since the polling timer just
1108 * started).
1109 */
1110 static void
restrainCursor(CompScreen * s,int out)1111 restrainCursor (CompScreen *s, int out)
1112 {
1113 int x1, y1, x2, y2, margin;
1114 int diffX = 0, diffY = 0;
1115 int north, south, east, west;
1116 float z;
1117 CompOutput *o = &s->outputDev[out];
1118 ZOOM_SCREEN (s);
1119 ZOOM_DISPLAY (s->display);
1120
1121 z = zs->zooms[out].newZoom;
1122 margin = zs->opt[SOPT_RESTRAIN_MARGIN].value.i;
1123 north = distanceToEdge (s, out, NORTH);
1124 south = distanceToEdge (s, out, SOUTH);
1125 east = distanceToEdge (s, out, EAST);
1126 west = distanceToEdge (s, out, WEST);
1127
1128 if (zs->zooms[out].currentZoom == 1.0f)
1129 {
1130 zs->lastChange = time(NULL);
1131 (*zd->mpFunc->getCurrentPosition) (s, &zs->mouseX, &zs->mouseY);
1132 }
1133
1134 convertToZoomedTarget (s, out, zs->mouseX - zs->cursor.hotX,
1135 zs->mouseY - zs->cursor.hotY, &x1, &y1);
1136 convertToZoomedTarget
1137 (s, out,
1138 zs->mouseX - zs->cursor.hotX + zs->cursor.width,
1139 zs->mouseY - zs->cursor.hotY + zs->cursor.height,
1140 &x2, &y2);
1141
1142 if ((x2 - x1 > o->region.extents.x2 - o->region.extents.x1) ||
1143 (y2 - y1 > o->region.extents.y2 - o->region.extents.y1))
1144 return;
1145 if (x2 > o->region.extents.x2 - margin && east > 0)
1146 diffX = x2 - o->region.extents.x2 + margin;
1147 else if (x1 < o->region.extents.x1 + margin && west > 0)
1148 diffX = x1 - o->region.extents.x1 - margin;
1149
1150 if (y2 > o->region.extents.y2 - margin && south > 0)
1151 diffY = y2 - o->region.extents.y2 + margin;
1152 else if (y1 < o->region.extents.y1 + margin && north > 0)
1153 diffY = y1 - o->region.extents.y1 - margin;
1154
1155 if (abs(diffX)*z > 0 || abs(diffY)*z > 0)
1156 warpPointer (s,
1157 (int) (zs->mouseX - pointerX) - (int) ((float)diffX * z),
1158 (int) (zs->mouseY - pointerY) - (int) ((float)diffY * z));
1159 }
1160
1161 /* Check if the cursor is still visible.
1162 * We also make sure to activate/deactivate cursor scaling here
1163 * so we turn on/off the pointer if it moves from one head to another.
1164 * FIXME: Detect an actual output change instead of spamming.
1165 * FIXME: The second ensureVisibility (sync with restrain).
1166 */
1167 static void
cursorMoved(CompScreen * s)1168 cursorMoved (CompScreen *s)
1169 {
1170 int out;
1171 ZOOM_SCREEN (s);
1172
1173 out = outputDeviceForPoint (s, zs->mouseX, zs->mouseY);
1174 if (isActive (s, out))
1175 {
1176 if (zs->opt[SOPT_RESTRAIN_MOUSE].value.b)
1177 restrainCursor (s, out);
1178
1179 if (zs->opt[SOPT_MOUSE_PAN].value.b)
1180 {
1181 ensureVisibilityArea (s,
1182 zs->mouseX - zs->cursor.hotX,
1183 zs->mouseY - zs->cursor.hotY,
1184 zs->mouseX + zs->cursor.width -
1185 zs->cursor.hotX,
1186 zs->mouseY + zs->cursor.height -
1187 zs->cursor.hotY,
1188 zs->opt[SOPT_RESTRAIN_MARGIN].value.i,
1189 NORTHWEST);
1190 }
1191
1192 cursorZoomActive (s);
1193 }
1194 else
1195 {
1196 cursorZoomInactive (s);
1197 }
1198 }
1199
1200 /* Update the mouse position.
1201 * Based on the zoom engine in use, we will have to move the zoom area.
1202 * This might have to be added to a timer.
1203 */
1204 static void
updateMousePosition(CompScreen * s,int x,int y)1205 updateMousePosition (CompScreen *s, int x, int y)
1206 {
1207 ZOOM_SCREEN(s);
1208 int out;
1209 zs->mouseX = x;
1210 zs->mouseY = y;
1211 out = outputDeviceForPoint (s, zs->mouseX, zs->mouseY);
1212 zs->lastChange = time(NULL);
1213 if (zs->opt[SOPT_SYNC_MOUSE].value.b && !isInMovement (s, out))
1214 setCenter (s, zs->mouseX, zs->mouseY, TRUE);
1215 cursorMoved (s);
1216 damageScreen (s);
1217 }
1218
1219 /* Timeout handler to poll the mouse. Returns false (and thereby does not
1220 * get re-added to the queue) when zoom is not active. */
1221 static void
updateMouseInterval(CompScreen * s,int x,int y)1222 updateMouseInterval (CompScreen *s, int x, int y)
1223 {
1224 ZOOM_SCREEN (s);
1225
1226 updateMousePosition(s, x, y);
1227
1228 if (!zs->grabbed)
1229 {
1230 ZOOM_DISPLAY (s->display);
1231 if (zs->pollHandle)
1232 (*zd->mpFunc->removePositionPolling) (s, zs->pollHandle);
1233 zs->pollHandle = 0;
1234 cursorMoved (s);
1235 }
1236 }
1237
1238 /* Free a cursor */
1239 static void
freeCursor(CursorTexture * cursor)1240 freeCursor (CursorTexture * cursor)
1241 {
1242 if (!cursor->isSet)
1243 return;
1244
1245 makeScreenCurrent (cursor->screen);
1246 cursor->isSet = FALSE;
1247 glDeleteTextures (1, &cursor->texture);
1248 cursor->texture = 0;
1249 }
1250
1251 /* Translate into place and draw the scaled cursor. */
1252 static void
drawCursor(CompScreen * s,CompOutput * output,const CompTransform * transform)1253 drawCursor (CompScreen *s,
1254 CompOutput *output,
1255 const CompTransform *transform)
1256 {
1257 int out = output->id;
1258 ZOOM_SCREEN (s);
1259
1260 if (zs->cursor.isSet)
1261 {
1262 CompTransform sTransform = *transform;
1263 float scaleFactor;
1264 int ax, ay, x, y;
1265
1266 /* This is a hack because these transformations are wrong when
1267 * we're working exposed. Expo is capable of telling where the
1268 * real mouse is despite zoom, so we don't have to disable the
1269 * zoom. We do, however, have to show the original pointer.
1270 */
1271 if (dontuseScreengrabExist (s, "expo"))
1272 {
1273 cursorZoomInactive (s);
1274 return;
1275 }
1276
1277 transformToScreenSpace (s, output, -DEFAULT_Z_CAMERA, &sTransform);
1278 convertToZoomed (s, out, zs->mouseX, zs->mouseY, &ax, &ay);
1279 glPushMatrix ();
1280 glLoadMatrixf (sTransform.m);
1281 glTranslatef ((float) ax, (float) ay, 0.0f);
1282 if (zs->opt[SOPT_SCALE_MOUSE_DYNAMIC].value.b)
1283 scaleFactor = 1.0f / zs->zooms[out].currentZoom;
1284 else
1285 scaleFactor = 1.0f / zs->opt[SOPT_SCALE_MOUSE_STATIC].value.f;
1286 glScalef (scaleFactor,
1287 scaleFactor,
1288 1.0f);
1289 x = -zs->cursor.hotX;
1290 y = -zs->cursor.hotY;
1291
1292 glEnable (GL_BLEND);
1293 glBindTexture (GL_TEXTURE_RECTANGLE_ARB, zs->cursor.texture);
1294 glEnable (GL_TEXTURE_RECTANGLE_ARB);
1295
1296 glBegin (GL_QUADS);
1297 glTexCoord2d (0, 0);
1298 glVertex2f (x, y);
1299 glTexCoord2d (0, zs->cursor.height);
1300 glVertex2f (x, y + zs->cursor.height);
1301 glTexCoord2d (zs->cursor.width, zs->cursor.height);
1302 glVertex2f (x + zs->cursor.width, y + zs->cursor.height);
1303 glTexCoord2d (zs->cursor.width, 0);
1304 glVertex2f (x + zs->cursor.width, y);
1305 glEnd ();
1306 glDisable (GL_BLEND);
1307 glBindTexture (GL_TEXTURE_RECTANGLE_ARB, 0);
1308 glDisable (GL_TEXTURE_RECTANGLE_ARB);
1309 glPopMatrix ();
1310 }
1311 }
1312
1313 /* Create (if necessary) a texture to store the cursor,
1314 * fetch the cursor with XFixes. Store it. */
1315 static void
zoomUpdateCursor(CompScreen * s,CursorTexture * cursor)1316 zoomUpdateCursor (CompScreen * s, CursorTexture * cursor)
1317 {
1318 unsigned char *pixels;
1319 int i;
1320 Display *dpy = s->display->display;
1321 ZOOM_SCREEN (s);
1322
1323 if (!cursor->isSet)
1324 {
1325 cursor->isSet = TRUE;
1326 cursor->screen = s;
1327 makeScreenCurrent (s);
1328 glEnable (GL_TEXTURE_RECTANGLE_ARB);
1329 glGenTextures (1, &cursor->texture);
1330 glBindTexture (GL_TEXTURE_RECTANGLE_ARB, cursor->texture);
1331
1332 if (zs->opt[SOPT_FILTER_LINEAR].value.b &&
1333 s->display->textureFilter != GL_NEAREST)
1334 {
1335 glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
1336 GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1337 glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
1338 GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1339 }
1340 else
1341 {
1342 glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
1343 GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1344 glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
1345 GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1346 }
1347 glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
1348 GL_TEXTURE_WRAP_S, GL_CLAMP);
1349 glTexParameteri (GL_TEXTURE_RECTANGLE_ARB,
1350 GL_TEXTURE_WRAP_T, GL_CLAMP);
1351 } else {
1352 makeScreenCurrent (cursor->screen);
1353 glEnable (GL_TEXTURE_RECTANGLE_ARB);
1354 }
1355
1356 XFixesCursorImage *ci = XFixesGetCursorImage(dpy);
1357 /* Hack to avoid changing to an invisible (bugged)cursor image.
1358 * Example: The animated Firefox cursors.
1359 */
1360 if (ci->width <= 1 && ci->height <= 1)
1361 {
1362 XFree (ci);
1363 return;
1364 }
1365
1366
1367 cursor->width = ci->width;
1368 cursor->height = ci->height;
1369 cursor->hotX = ci->xhot;
1370 cursor->hotY = ci->yhot;
1371 pixels = malloc(ci->width * ci->height * 4);
1372
1373 if (!pixels)
1374 {
1375 XFree (ci);
1376 return;
1377 }
1378
1379 for (i = 0; i < ci->width * ci->height; i++)
1380 {
1381 unsigned long pix = ci->pixels[i];
1382 pixels[i * 4] = pix & 0xff;
1383 pixels[(i * 4) + 1] = (pix >> 8) & 0xff;
1384 pixels[(i * 4) + 2] = (pix >> 16) & 0xff;
1385 pixels[(i * 4) + 3] = (pix >> 24) & 0xff;
1386 }
1387
1388 glBindTexture (GL_TEXTURE_RECTANGLE_ARB, cursor->texture);
1389 glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, cursor->width,
1390 cursor->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
1391 glBindTexture (GL_TEXTURE_RECTANGLE_ARB, 0);
1392 glDisable (GL_TEXTURE_RECTANGLE_ARB);
1393 XFree (ci);
1394 free (pixels);
1395 }
1396
1397 /* We are no longer zooming the cursor, so display it. */
1398 static void
cursorZoomInactive(CompScreen * s)1399 cursorZoomInactive (CompScreen *s)
1400 {
1401 ZOOM_DISPLAY (s->display);
1402 ZOOM_SCREEN (s);
1403
1404 if (!zd->fixesSupported)
1405 return;
1406
1407 if (zs->cursorInfoSelected)
1408 {
1409 zs->cursorInfoSelected = FALSE;
1410 XFixesSelectCursorInput (s->display->display, s->root, 0);
1411 }
1412
1413 if (zs->cursor.isSet)
1414 {
1415 freeCursor (&zs->cursor);
1416 }
1417
1418 if (zs->cursorHidden)
1419 {
1420 zs->cursorHidden = FALSE;
1421 XFixesShowCursor (s->display->display, s->root);
1422 }
1423 }
1424
1425 /* Cursor zoom is active: We need to hide the original,
1426 * register for Cursor notifies and display the new one.
1427 * This can be called multiple times, not just on initial
1428 * activation.
1429 */
1430 static void
cursorZoomActive(CompScreen * s)1431 cursorZoomActive (CompScreen *s)
1432 {
1433 ZOOM_DISPLAY (s->display);
1434 ZOOM_SCREEN (s);
1435
1436 if (!zd->fixesSupported)
1437 return;
1438 if (!zs->opt[SOPT_SCALE_MOUSE].value.b)
1439 return;
1440
1441 if (!zs->cursorInfoSelected)
1442 {
1443 zs->cursorInfoSelected = TRUE;
1444 XFixesSelectCursorInput (s->display->display, s->root,
1445 XFixesDisplayCursorNotifyMask);
1446 zoomUpdateCursor (s, &zs->cursor);
1447 }
1448 if (zd->canHideCursor && !zs->cursorHidden &&
1449 zs->opt[SOPT_HIDE_ORIGINAL_MOUSE].value.b)
1450 {
1451 zs->cursorHidden = TRUE;
1452 XFixesHideCursor (s->display->display, s->root);
1453 }
1454 }
1455
1456 /* Set the zoom area
1457 * This is an interface for scripting.
1458 * int32:x1: left x coordinate
1459 * int32:y1: top y coordinate
1460 * int32:x2: right x
1461 * int32:y2: bottom y
1462 * x2 and y2 can be omitted to assume x1==x2+1 y1==y2+1
1463 * boolean:scale: True if we should modify the zoom level, false to just
1464 * adjust the movement/translation.
1465 * boolean:restrain: True to warp the pointer so it's visible.
1466 */
1467 static Bool
setZoomAreaAction(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1468 setZoomAreaAction (CompDisplay *d,
1469 CompAction *action,
1470 CompActionState state,
1471 CompOption *option,
1472 int nOption)
1473 {
1474 CompScreen *s;
1475 Window xid;
1476
1477 xid = getIntOptionNamed (option, nOption, "root", 0);
1478 s = findScreenAtDisplay (d, xid);
1479
1480 if (s)
1481 {
1482 int x1, y1, x2, y2, out;
1483 Bool scale, restrain;
1484 CompOutput *o;
1485
1486 x1 = getIntOptionNamed (option, nOption, "x1", -1);
1487 y1 = getIntOptionNamed (option, nOption, "y1", -1);
1488 x2 = getIntOptionNamed (option, nOption, "x2", -1);
1489 y2 = getIntOptionNamed (option, nOption, "y2", -1);
1490 scale = getBoolOptionNamed (option, nOption, "scale", FALSE);
1491 restrain = getBoolOptionNamed (option, nOption, "restrain", FALSE);
1492
1493 if (x1 < 0 || y1 < 0)
1494 return FALSE;
1495
1496 if (x2 < 0)
1497 x2 = x1 + 1;
1498
1499 if (y2 < 0)
1500 y2 = y1 + 1;
1501
1502 out = outputDeviceForPoint (s, x1, y1);
1503 #define WIDTH (x2 - x1)
1504 #define HEIGHT (y2 - y1)
1505 setZoomArea (s, x1, y1, WIDTH, HEIGHT, FALSE);
1506 o = &s->outputDev[out];
1507 if (scale && WIDTH && HEIGHT)
1508 setScaleBigger (s, out, (float) WIDTH/o->width,
1509 (float) HEIGHT/o->height);
1510 #undef WIDTH
1511 #undef HEIGHT
1512 if (restrain)
1513 restrainCursor (s, out);
1514 }
1515 return TRUE;
1516 }
1517
1518 /* Ensure visibility of an area defined by x1->x2/y1->y2
1519 * int:x1: left X coordinate
1520 * int:x2: right X Coordinate
1521 * int:y1: top Y coordinate
1522 * int:y2: bottom Y coordinate
1523 * bool:scale: zoom out if necessary to ensure visibility
1524 * bool:restrain: Restrain the mouse cursor
1525 * int:margin: The margin to use (default: 0)
1526 * if x2/y2 is omitted, it is ignored.
1527 */
1528 static Bool
ensureVisibilityAction(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1529 ensureVisibilityAction (CompDisplay *d,
1530 CompAction *action,
1531 CompActionState state,
1532 CompOption *option,
1533 int nOption)
1534 {
1535 CompScreen *s;
1536 Window xid;
1537
1538 xid = getIntOptionNamed (option, nOption, "root", 0);
1539 s = findScreenAtDisplay (d, xid);
1540
1541 if (s)
1542 {
1543 int x1, y1, x2, y2, margin, out;
1544 Bool scale, restrain;
1545 CompOutput *o;
1546
1547 x1 = getIntOptionNamed (option, nOption, "x1", -1);
1548 y1 = getIntOptionNamed (option, nOption, "y1", -1);
1549 x2 = getIntOptionNamed (option, nOption, "x2", -1);
1550 y2 = getIntOptionNamed (option, nOption, "y2", -1);
1551 margin = getBoolOptionNamed (option, nOption, "margin", 0);
1552 scale = getBoolOptionNamed (option, nOption, "scale", FALSE);
1553 restrain = getBoolOptionNamed (option, nOption, "restrain", FALSE);
1554 if (x1 < 0 || y1 < 0)
1555 return FALSE;
1556 if (x2 < 0)
1557 y2 = y1 + 1;
1558 out = outputDeviceForPoint (s, x1, y1);
1559 ensureVisibility (s, x1, y1, margin);
1560 if (x2 >= 0 && y2 >= 0)
1561 ensureVisibility (s, x2, y2, margin);
1562 o = &s->outputDev[out];
1563 #define WIDTH (x2 - x1)
1564 #define HEIGHT (y2 - y1)
1565 if (scale && WIDTH && HEIGHT)
1566 setScaleBigger (s, out, (float) WIDTH/o->width,
1567 (float) HEIGHT/o->height);
1568 #undef WIDTH
1569 #undef HEIGHT
1570 if (restrain)
1571 restrainCursor (s, out);
1572 }
1573 return TRUE;
1574 }
1575
1576 static Bool
zoomBoxActivate(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1577 zoomBoxActivate (CompDisplay *d,
1578 CompAction *action,
1579 CompActionState state,
1580 CompOption *option,
1581 int nOption)
1582 {
1583 CompScreen *s;
1584 int xid;
1585
1586 xid = getIntOptionNamed (option, nOption, "root", 0);
1587 s = findScreenAtDisplay (d, xid);
1588
1589 if (s)
1590 {
1591 ZOOM_SCREEN (s);
1592 zs->grabIndex = pushScreenGrab (s, None, "ezoom");
1593 zs->box.x1 = pointerX;
1594 zs->box.y1 = pointerY;
1595 zs->box.x2 = pointerX;
1596 zs->box.y2 = pointerY;
1597 if (state & CompActionStateInitButton)
1598 action->state |= CompActionStateTermButton;
1599 return TRUE;
1600 }
1601 return FALSE;
1602 }
1603
1604 static Bool
zoomBoxDeactivate(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1605 zoomBoxDeactivate (CompDisplay *d,
1606 CompAction *action,
1607 CompActionState state,
1608 CompOption *option,
1609 int nOption)
1610 {
1611 CompScreen *s;
1612 int xid;
1613
1614 xid = getIntOptionNamed (option, nOption, "root", 0);
1615
1616 for (s = d->screens; s; s = s->next)
1617 {
1618 int x, y, width, height;
1619 ZOOM_SCREEN (s);
1620
1621 if (zs->grabIndex)
1622 {
1623 int out;
1624 CompOutput *o;
1625
1626 removeScreenGrab (s, zs->grabIndex, NULL);
1627 zs->grabIndex = 0;
1628
1629 zs->box.x2 = pointerX;
1630 zs->box.y2 = pointerY;
1631
1632 x = MIN (zs->box.x1, zs->box.x2);
1633 y = MIN (zs->box.y1, zs->box.y2);
1634 width = MAX (zs->box.x1, zs->box.x2) - x;
1635 height = MAX (zs->box.y1, zs->box.y2) - y;
1636
1637 out = outputDeviceForGeometry (s, x,y,width,height,0);
1638 o = &s->outputDev[out];
1639 setScaleBigger (s, out, (float) width/o->width, (float)
1640 height/o->height);
1641 setZoomArea (s, x,y,width,height,FALSE);
1642 }
1643 }
1644 return FALSE;
1645 }
1646
1647 /* Zoom in to the area pointed to by the mouse.
1648 */
1649 static Bool
zoomIn(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1650 zoomIn (CompDisplay *d,
1651 CompAction *action,
1652 CompActionState state,
1653 CompOption *option,
1654 int nOption)
1655 {
1656 CompScreen *s;
1657 Window xid;
1658
1659 xid = getIntOptionNamed (option, nOption, "root", 0);
1660 s = findScreenAtDisplay (d, xid);
1661
1662 if (s)
1663 {
1664 int out = outputDeviceForPoint (s, pointerX, pointerY);
1665 ZOOM_SCREEN (s);
1666
1667 if (zs->opt[SOPT_SYNC_MOUSE].value.b && !isInMovement (s, out))
1668 setCenter (s, pointerX, pointerY, TRUE);
1669
1670 setScale (s, out,
1671 zs->zooms[out].newZoom /
1672 zs->opt[SOPT_ZOOM_FACTOR].value.f);
1673 }
1674 return TRUE;
1675 }
1676
1677 /* Locks down the current zoom area
1678 */
1679 static Bool
lockZoomAction(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1680 lockZoomAction (CompDisplay *d,
1681 CompAction *action,
1682 CompActionState state,
1683 CompOption *option,
1684 int nOption)
1685 {
1686 CompScreen *s;
1687 Window xid;
1688
1689 xid = getIntOptionNamed (option, nOption, "root", 0);
1690 s = findScreenAtDisplay (d, xid);
1691
1692 if (s)
1693 {
1694 int out = outputDeviceForPoint (s, pointerX, pointerY);
1695 ZOOM_SCREEN (s);
1696 zs->zooms[out].locked = !zs->zooms[out].locked;
1697 }
1698 return TRUE;
1699 }
1700
1701 /* Zoom to a specific level.
1702 * target defines the target zoom level.
1703 * First set the scale level and mark the display as grabbed internally (to
1704 * catch the FocusIn event). Either target the focused window or the mouse,
1705 * depending on settings.
1706 * FIXME: A bit of a mess...
1707 */
1708 static Bool
zoomSpecific(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption,float target)1709 zoomSpecific (CompDisplay *d,
1710 CompAction *action,
1711 CompActionState state,
1712 CompOption *option,
1713 int nOption,
1714 float target)
1715 {
1716 CompScreen *s;
1717 Window xid;
1718
1719 xid = getIntOptionNamed (option, nOption, "root", 0);
1720 s = findScreenAtDisplay (d, xid);
1721
1722 if (s)
1723 {
1724 int x, y;
1725 int out = outputDeviceForPoint (s, pointerX, pointerY);
1726 CompWindow *w;
1727 ZOOM_DISPLAY (d);
1728 ZOOM_SCREEN (s);
1729
1730 if (target == 1.0f && zs->zooms[out].newZoom == 1.0f)
1731 return FALSE;
1732 if (otherScreenGrabExist (s, NULL))
1733 return FALSE;
1734
1735 setScale (s, out, target);
1736
1737 w = findWindowAtDisplay(d, d->activeWindow);
1738 if (zd->opt[DOPT_SPECIFIC_TARGET_FOCUS].value.b
1739 && w && w->screen->root == s->root)
1740 {
1741 zoomAreaToWindow (w);
1742 }
1743 else
1744 {
1745 x = getIntOptionNamed (option, nOption, "x", 0);
1746 y = getIntOptionNamed (option, nOption, "y", 0);
1747 setCenter (s, x, y, FALSE);
1748 }
1749 }
1750 return TRUE;
1751 }
1752
1753 static Bool
zoomSpecific1(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1754 zoomSpecific1 (CompDisplay *d,
1755 CompAction *action,
1756 CompActionState state,
1757 CompOption *option,
1758 int nOption)
1759 {
1760 ZOOM_DISPLAY (d);
1761
1762 return zoomSpecific (d, action, state, option, nOption,
1763 zd->opt[DOPT_SPECIFIC_LEVEL_1].value.f);
1764 }
1765
1766 static Bool
zoomSpecific2(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1767 zoomSpecific2 (CompDisplay *d,
1768 CompAction *action,
1769 CompActionState state,
1770 CompOption *option,
1771 int nOption)
1772 {
1773 ZOOM_DISPLAY (d);
1774
1775 return zoomSpecific (d, action, state, option, nOption,
1776 zd->opt[DOPT_SPECIFIC_LEVEL_2].value.f);
1777 }
1778
1779 static Bool
zoomSpecific3(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1780 zoomSpecific3 (CompDisplay *d,
1781 CompAction *action,
1782 CompActionState state,
1783 CompOption *option,
1784 int nOption)
1785 {
1786 ZOOM_DISPLAY (d);
1787
1788 return zoomSpecific (d, action, state, option, nOption,
1789 zd->opt[DOPT_SPECIFIC_LEVEL_3].value.f);
1790 }
1791
1792 /* Zooms to fit the active window to the screen without cutting
1793 * it off and targets it.
1794 */
1795 static Bool
zoomToWindow(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1796 zoomToWindow (CompDisplay *d,
1797 CompAction *action,
1798 CompActionState state,
1799 CompOption *option,
1800 int nOption)
1801 {
1802 int width, height, out;
1803 Window xid;
1804 CompScreen *s;
1805 CompWindow *w;
1806 CompOutput *o;
1807
1808 xid = getIntOptionNamed (option, nOption, "root", 0);
1809 s = findScreenAtDisplay (d, xid);
1810
1811 if (!s)
1812 return TRUE;
1813
1814 xid = getIntOptionNamed (option, nOption, "window", 0);
1815 w = findWindowAtDisplay (d, xid);
1816 if (!w || w->screen->root != s->root)
1817 return TRUE;
1818 width = w->width + w->input.left + w->input.right;
1819 height = w->height + w->input.top + w->input.bottom;
1820 out = outputDeviceForWindow (w);
1821 o = &s->outputDev[out];
1822 setScaleBigger (s, out, (float) width/o->width,
1823 (float) height/o->height);
1824 zoomAreaToWindow (w);
1825 return TRUE;
1826 }
1827
1828 static Bool
zoomPanLeft(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1829 zoomPanLeft (CompDisplay *d,
1830 CompAction *action,
1831 CompActionState state,
1832 CompOption *option,
1833 int nOption)
1834 {
1835 CompScreen *s;
1836 Window xid;
1837
1838 xid = getIntOptionNamed (option, nOption, "root", 0);
1839 s = findScreenAtDisplay (d, xid);
1840 if (!s)
1841 return TRUE;
1842
1843 panZoom (s, -1, 0);
1844 return TRUE;
1845 }
1846 static Bool
zoomPanRight(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1847 zoomPanRight (CompDisplay *d,
1848 CompAction *action,
1849 CompActionState state,
1850 CompOption *option,
1851 int nOption)
1852 {
1853 CompScreen *s;
1854 Window xid;
1855
1856 xid = getIntOptionNamed (option, nOption, "root", 0);
1857 s = findScreenAtDisplay (d, xid);
1858 if (!s)
1859 return TRUE;
1860 panZoom (s, 1, 0);
1861 return TRUE;
1862 }
1863 static Bool
zoomPanUp(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1864 zoomPanUp (CompDisplay *d,
1865 CompAction *action,
1866 CompActionState state,
1867 CompOption *option,
1868 int nOption)
1869 {
1870 CompScreen *s;
1871 Window xid;
1872
1873 xid = getIntOptionNamed (option, nOption, "root", 0);
1874 s = findScreenAtDisplay (d, xid);
1875 if (!s)
1876 return TRUE;
1877 panZoom (s, 0, -1);
1878 return TRUE;
1879 }
1880
1881 static Bool
zoomPanDown(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1882 zoomPanDown (CompDisplay *d,
1883 CompAction *action,
1884 CompActionState state,
1885 CompOption *option,
1886 int nOption)
1887 {
1888 CompScreen *s;
1889 Window xid;
1890
1891 xid = getIntOptionNamed (option, nOption, "root", 0);
1892 s = findScreenAtDisplay (d, xid);
1893 if (!s)
1894 return TRUE;
1895 panZoom (s, 0, 1);
1896 return TRUE;
1897 }
1898
1899 /* Centers the mouse based on zoom level and translation.
1900 */
1901 static Bool
zoomCenterMouse(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1902 zoomCenterMouse (CompDisplay *d,
1903 CompAction *action,
1904 CompActionState state,
1905 CompOption *option,
1906 int nOption)
1907 {
1908 int out;
1909 CompScreen *s;
1910 Window xid;
1911 ZoomScreen *zs;
1912
1913 xid = getIntOptionNamed (option, nOption, "root", 0);
1914 s = findScreenAtDisplay (d, xid);
1915 if (!s)
1916 return TRUE;
1917
1918 zs = GET_ZOOM_SCREEN (s, GET_ZOOM_DISPLAY (d));
1919 out = outputDeviceForPoint (s, pointerX, pointerY);
1920 warpPointer (s,
1921 (int) (s->outputDev[out].width/2 +
1922 s->outputDev[out].region.extents.x1 - pointerX)
1923 + ((float) s->outputDev[out].width *
1924 -zs->zooms[out].xtrans),
1925 (int) (s->outputDev[out].height/2 +
1926 s->outputDev[out].region.extents.y1 - pointerY)
1927 + ((float) s->outputDev[out].height *
1928 zs->zooms[out].ytrans));
1929 return TRUE;
1930 }
1931
1932 /* Resize a window to fit the zoomed area.
1933 * This could probably do with some moving-stuff too.
1934 * IE: Move the zoom area afterwards. And ensure
1935 * the window isn't resized off-screen.
1936 */
1937 static Bool
zoomFitWindowToZoom(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1938 zoomFitWindowToZoom (CompDisplay *d,
1939 CompAction *action,
1940 CompActionState state,
1941 CompOption *option,
1942 int nOption)
1943 {
1944 int out;
1945 unsigned int mask = CWWidth | CWHeight;
1946 Window xid;
1947 CompScreen *s;
1948 XWindowChanges xwc;
1949 CompWindow *w;
1950 ZoomScreen *zs;
1951
1952 xid = getIntOptionNamed (option, nOption, "window", 0);
1953 w = findWindowAtDisplay (d, xid);
1954 if (!w)
1955 return TRUE;
1956
1957 s = w->screen;
1958 out = outputDeviceForWindow (w);
1959 zs = GET_ZOOM_SCREEN (s, GET_ZOOM_DISPLAY (d));
1960 xwc.x = w->serverX;
1961 xwc.y = w->serverY;
1962 xwc.width = (int) (s->outputDev[out].width *
1963 zs->zooms[out].currentZoom -
1964 (int) ((w->input.left + w->input.right)));
1965 xwc.height = (int) (s->outputDev[out].height *
1966 zs->zooms[out].currentZoom -
1967 (int) ((w->input.top + w->input.bottom)));
1968
1969 constrainNewWindowSize (w,
1970 xwc.width,
1971 xwc.height,
1972 &xwc.width,
1973 &xwc.height);
1974
1975 if (xwc.width == w->serverWidth)
1976 mask &= ~CWWidth;
1977
1978 if (xwc.height == w->serverHeight)
1979 mask &= ~CWHeight;
1980
1981 if (w->mapNum && (mask & (CWWidth | CWHeight)))
1982 sendSyncRequest (w);
1983
1984 configureXWindow (w, mask, &xwc);
1985 return TRUE;
1986 }
1987
1988 static Bool
zoomInitiate(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)1989 zoomInitiate (CompDisplay *d,
1990 CompAction *action,
1991 CompActionState state,
1992 CompOption *option,
1993 int nOption)
1994 {
1995 zoomIn (d, action, state, option, nOption);
1996
1997 if (state & CompActionStateInitKey)
1998 action->state |= CompActionStateTermKey;
1999
2000 if (state & CompActionStateInitButton)
2001 action->state |= CompActionStateTermButton;
2002
2003 return TRUE;
2004 }
2005
2006 static Bool
zoomOut(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)2007 zoomOut (CompDisplay *d,
2008 CompAction *action,
2009 CompActionState state,
2010 CompOption *option,
2011 int nOption)
2012 {
2013 CompScreen *s;
2014 Window xid;
2015
2016 xid = getIntOptionNamed (option, nOption, "root", 0);
2017 s = findScreenAtDisplay (d, xid);
2018 if (s)
2019 {
2020 int out = outputDeviceForPoint (s, pointerX, pointerY);
2021 ZOOM_SCREEN (s);
2022
2023 setScale (s, out,
2024 zs->zooms[out].newZoom *
2025 zs->opt[SOPT_ZOOM_FACTOR].value.f);
2026 }
2027
2028 return TRUE;
2029 }
2030
2031 static Bool
zoomTerminate(CompDisplay * d,CompAction * action,CompActionState state,CompOption * option,int nOption)2032 zoomTerminate (CompDisplay *d,
2033 CompAction *action,
2034 CompActionState state,
2035 CompOption *option,
2036 int nOption)
2037 {
2038 CompScreen *s;
2039 Window xid;
2040
2041 xid = getIntOptionNamed (option, nOption, "root", 0);
2042
2043 for (s = d->screens; s; s = s->next)
2044 {
2045 int out;
2046 ZOOM_SCREEN (s);
2047
2048 if (xid && s->root != xid)
2049 continue;
2050
2051 out = outputDeviceForPoint (s, pointerX, pointerY);
2052
2053 if (zs->grabbed)
2054 {
2055 zs->zooms[out].newZoom = 1.0f;
2056 damageScreen (s);
2057 }
2058 }
2059 action->state &= ~(CompActionStateTermKey | CompActionStateTermButton);
2060 return FALSE;
2061 }
2062
2063 /* Focus-track related event handling.
2064 * The lastMapped is a hack to ensure that newly mapped windows are
2065 * caught even if the grab that (possibly) triggered them affected
2066 * the mode. Windows created by a key binding (like creating a terminal
2067 * on a key binding) tends to trigger FocusIn events with mode other than
2068 * Normal. This works around this problem.
2069 * FIXME: Cleanup.
2070 * TODO: Avoid maximized windows.
2071 */
2072 static void
focusTrack(CompDisplay * d,XEvent * event)2073 focusTrack (CompDisplay *d,
2074 XEvent *event)
2075 {
2076 int out;
2077 static Window lastMapped = 0;
2078 CompWindow *w;
2079 ZoomScreen *zs;
2080
2081 if (event->type == MapNotify)
2082 {
2083 lastMapped = event->xmap.window;
2084 return;
2085 }
2086 else if (event->type != FocusIn)
2087 return;
2088
2089 if ((event->xfocus.mode != NotifyNormal)
2090 && (lastMapped != event->xfocus.window))
2091 return;
2092
2093 lastMapped = 0;
2094 w = findWindowAtDisplay(d, event->xfocus.window);
2095 if (w == NULL || w->id == d->activeWindow)
2096 return;
2097
2098 zs = GET_ZOOM_SCREEN (w->screen, GET_ZOOM_DISPLAY (d));
2099
2100 if (time(NULL) - zs->lastChange < zs->opt[SOPT_FOCUS_DELAY].value.i ||
2101 !zs->opt[SOPT_FOLLOW_FOCUS].value.b)
2102 return;
2103
2104 out = outputDeviceForWindow (w);
2105 if (!isActive (w->screen, out) &&
2106 !zs->opt[SOPT_ALLWAYS_FOCUS_FIT_WINDOW].value.b)
2107 return;
2108 if (zs->opt[SOPT_FOCUS_FIT_WINDOW].value.b)
2109 {
2110 int width = w->width + w->input.left + w->input.right;
2111 int height = w->height + w->input.top + w->input.bottom;
2112 float scale = MAX ((float) width/w->screen->outputDev[out].width,
2113 (float) height/w->screen->outputDev[out].height);
2114 if (scale > zs->opt[SOPT_AUTOSCALE_MIN].value.f)
2115 setScale (w->screen, out, scale);
2116 }
2117 zoomAreaToWindow (w);
2118 }
2119
2120 /* Event handler. Pass focus-related events on and handle XFixes events. */
2121 static void
zoomHandleEvent(CompDisplay * d,XEvent * event)2122 zoomHandleEvent (CompDisplay *d,
2123 XEvent *event)
2124 {
2125 CompScreen *s;
2126 XMotionEvent *mev;
2127 ZOOM_DISPLAY(d);
2128
2129 switch (event->type) {
2130 case MotionNotify:
2131 mev = (XMotionEvent *) event;
2132 s = findScreenAtDisplay (d, mev->root);
2133 if (s)
2134 {
2135 ZOOM_SCREEN (s);
2136 if (zs->grabIndex)
2137 {
2138 zs->box.x2 = pointerX;
2139 zs->box.y2 = pointerY;
2140 damageScreen(s);
2141 }
2142 }
2143 break;
2144 case FocusIn:
2145 case MapNotify:
2146 focusTrack (d, event);
2147 break;
2148 default:
2149 if (event->type == zd->fixesEventBase + XFixesCursorNotify)
2150 {
2151 XFixesCursorNotifyEvent *cev = (XFixesCursorNotifyEvent *)
2152 event;
2153 s = findScreenAtDisplay (d, cev->window);
2154 if (s)
2155 {
2156 ZOOM_SCREEN(s);
2157 if (zs->cursor.isSet)
2158 zoomUpdateCursor (s, &zs->cursor);
2159 }
2160 }
2161 break;
2162 }
2163 UNWRAP (zd, d, handleEvent);
2164 (*d->handleEvent) (d, event);
2165 WRAP (zd, d, handleEvent, zoomHandleEvent);
2166 }
2167
2168 /* Settings etc., boring stuff */
2169 static const CompMetadataOptionInfo zoomDisplayOptionInfo[] = {
2170 { "initiate", "key", 0, zoomInitiate, zoomTerminate },
2171 { "zoom_in_button", "button", 0, zoomIn, 0 },
2172 { "zoom_out_button", "button", 0, zoomOut, 0 },
2173 { "zoom_in_key", "key", 0, zoomIn, 0 },
2174 { "zoom_out_key", "key", 0, zoomOut, 0 },
2175 { "zoom_specific_1_key", "key", 0, zoomSpecific1, 0 },
2176 { "zoom_specific_2_key", "key", 0, zoomSpecific2, 0 },
2177 { "zoom_specific_3_key", "key", 0, zoomSpecific3, 0 },
2178 { "zoom_spec1", "float", "<min>0.1</min><max>1.0</max><default>1.0</default>", 0, 0 },
2179 { "zoom_spec2", "float", "<min>0.1</min><max>1.0</max><default>0.5</default>", 0, 0 },
2180 { "zoom_spec3", "float", "<min>0.1</min><max>1.0</max><default>0.2</default>", 0, 0 },
2181 { "spec_target_focus", "bool", "<default>true</default>", 0, 0 },
2182 { "pan_left_key", "key", 0, zoomPanLeft, 0 },
2183 { "pan_right_key", "key", 0, zoomPanRight, 0 },
2184 { "pan_up_key", "key", 0, zoomPanUp, 0 },
2185 { "pan_down_key", "key", 0, zoomPanDown, 0 },
2186 { "fit_to_window_key", "key", 0, zoomToWindow, 0 },
2187 { "center_mouse_key", "key", 0, zoomCenterMouse, 0 },
2188 { "fit_to_zoom_key", "key", 0, zoomFitWindowToZoom, 0 },
2189 { "ensure_visibility", "action", 0, ensureVisibilityAction, 0},
2190 { "set_zoom_area", "action", 0, setZoomAreaAction, 0},
2191 { "lock_zoom_key", "key", 0, lockZoomAction, 0},
2192 { "zoom_box_button", "button", 0, zoomBoxActivate, zoomBoxDeactivate},
2193 };
2194
2195 static const CompMetadataOptionInfo zoomScreenOptionInfo[] = {
2196 { "follow_focus", "bool", 0, 0, 0 },
2197 { "speed", "float", "<min>0.01</min>", 0, 0 },
2198 { "timestep", "float", "<min>0.1</min>", 0, 0 },
2199 { "zoom_factor", "float", "<min>1.01</min>", 0, 0 },
2200 { "filter_linear", "bool", 0, 0, 0 },
2201 { "sync_mouse", "bool", 0, 0, 0 },
2202 { "follow_focus_delay", "int", "<min>0</min>", 0, 0 },
2203 { "pan_factor", "float", "<min>0.001</min><default>0.1</default>", 0, 0 },
2204 { "focus_fit_window", "bool", "<default>false</default>", 0, 0 },
2205 { "always_focus_fit_window", "bool", "<default>false</default>", 0, 0 },
2206 { "scale_mouse", "bool", "<default>false</default>", 0, 0 },
2207 { "scale_mouse_dynamic", "bool", "<default>true</default>", 0, 0 },
2208 { "scale_mouse_static", "float", "<default>0.8</default>", 0, 0 },
2209 { "hide_original_mouse", "bool", "<default>false</default>", 0, 0 },
2210 { "restrain_mouse", "bool", "<default>false</default>", 0, 0 },
2211 { "restrain_margin", "int", "<default>5</default>", 0, 0 },
2212 { "mouse_pan", "bool", "<default>false</default>", 0, 0 },
2213 { "minimum_zoom", "float", "<max>1.00</max>", 0, 0 },
2214 { "autoscale_min", "float", "<max>1.00</max>", 0, 0 }
2215 };
2216
2217 static CompOption *
zoomGetScreenOptions(CompPlugin * plugin,CompScreen * screen,int * count)2218 zoomGetScreenOptions (CompPlugin *plugin,
2219 CompScreen *screen,
2220 int *count)
2221 {
2222 ZOOM_SCREEN (screen);
2223
2224 *count = NUM_OPTIONS (zs);
2225 return zs->opt;
2226 }
2227
2228 static Bool
zoomSetScreenOption(CompPlugin * plugin,CompScreen * screen,const char * name,CompOptionValue * value)2229 zoomSetScreenOption (CompPlugin *plugin,
2230 CompScreen *screen,
2231 const char *name,
2232 CompOptionValue *value)
2233 {
2234 CompOption *o;
2235 int index;
2236
2237 ZOOM_SCREEN (screen);
2238
2239 o = compFindOption (zs->opt, NUM_OPTIONS (zs), name, &index);
2240 if (!o)
2241 return FALSE;
2242
2243 return compSetScreenOption (screen, o, value);
2244 }
2245
2246 static CompOption *
zoomGetDisplayOptions(CompPlugin * plugin,CompDisplay * display,int * count)2247 zoomGetDisplayOptions (CompPlugin *plugin,
2248 CompDisplay *display,
2249 int *count)
2250 {
2251 ZOOM_DISPLAY (display);
2252 *count = NUM_OPTIONS (zd);
2253 return zd->opt;
2254 }
2255
2256 static Bool
zoomSetDisplayOption(CompPlugin * plugin,CompDisplay * display,const char * name,CompOptionValue * value)2257 zoomSetDisplayOption (CompPlugin *plugin,
2258 CompDisplay *display,
2259 const char *name,
2260 CompOptionValue *value)
2261 {
2262 CompOption *o;
2263 int index;
2264 ZOOM_DISPLAY (display);
2265
2266 o = compFindOption (zd->opt, NUM_OPTIONS (zd), name, &index);
2267 if (!o)
2268 return FALSE;
2269
2270 return compSetDisplayOption (display, o, value);
2271 }
2272
2273
2274 static Bool
zoomInitDisplay(CompPlugin * p,CompDisplay * d)2275 zoomInitDisplay (CompPlugin *p,
2276 CompDisplay *d)
2277 {
2278 int minor, major;
2279 int index;
2280 ZoomDisplay *zd;
2281
2282 if (!checkPluginABI ("core", CORE_ABIVERSION))
2283 return FALSE;
2284
2285 if (!checkPluginABI ("mousepoll", MOUSEPOLL_ABIVERSION))
2286 return FALSE;
2287
2288 if (!getPluginDisplayIndex (d, "mousepoll", &index))
2289 return FALSE;
2290
2291 zd = malloc (sizeof (ZoomDisplay));
2292 if (!zd)
2293 return FALSE;
2294 if (!compInitDisplayOptionsFromMetadata (d,
2295 &zoomMetadata,
2296 zoomDisplayOptionInfo,
2297 zd->opt,
2298 DOPT_NUM))
2299 {
2300 free (zd);
2301 return FALSE;
2302 }
2303
2304 zd->mpFunc = d->base.privates[index].ptr;
2305 zd->screenPrivateIndex = allocateScreenPrivateIndex (d);
2306 if (zd->screenPrivateIndex < 0)
2307 {
2308 compFiniDisplayOptions (d, zd->opt, DOPT_NUM);
2309 free (zd);
2310 return FALSE;
2311 }
2312
2313 zd->fixesSupported =
2314 XFixesQueryExtension(d->display,
2315 &zd->fixesEventBase,
2316 &zd->fixesErrorBase);
2317
2318 XFixesQueryVersion(d->display, &major, &minor);
2319 if (major >= 4)
2320 zd->canHideCursor = TRUE;
2321 else
2322 zd->canHideCursor = FALSE;
2323
2324 WRAP (zd, d, handleEvent, zoomHandleEvent);
2325 d->base.privates[displayPrivateIndex].ptr = zd;
2326 return TRUE;
2327 }
2328
2329 static void
zoomFiniDisplay(CompPlugin * p,CompDisplay * d)2330 zoomFiniDisplay (CompPlugin *p,
2331 CompDisplay *d)
2332 {
2333 ZOOM_DISPLAY (d);
2334 freeScreenPrivateIndex (d, zd->screenPrivateIndex);
2335 UNWRAP (zd, d, handleEvent);
2336 compFiniDisplayOptions (d, zd->opt, DOPT_NUM);
2337 free (zd);
2338 }
2339
2340 static Bool
zoomInitScreen(CompPlugin * p,CompScreen * s)2341 zoomInitScreen (CompPlugin *p,
2342 CompScreen *s)
2343 {
2344 int i;
2345 ZoomScreen *zs;
2346 ZOOM_DISPLAY (s->display);
2347
2348 zs = malloc (sizeof (ZoomScreen));
2349 if (!zs)
2350 return FALSE;
2351
2352 if (!compInitScreenOptionsFromMetadata (s,
2353 &zoomMetadata,
2354 zoomScreenOptionInfo,
2355 zs->opt,
2356 SOPT_NUM))
2357 {
2358 free (zs);
2359 return FALSE;
2360 }
2361
2362 zs->grabIndex = 0;
2363 zs->nZooms = s->nOutputDev;
2364 zs->zooms = malloc (sizeof (ZoomArea) * zs->nZooms);
2365 for (i = 0; i < zs->nZooms; i ++ )
2366 {
2367 /* zs->grabbed is a mask ... Thus this limit */
2368 if (i > sizeof (long int) * 8)
2369 break;
2370 initialiseZoomArea (&zs->zooms[i], i);
2371 }
2372 zs->lastChange = 0;
2373 zs->grabbed = 0;
2374 zs->mouseX = -1;
2375 zs->mouseY = -1;
2376 zs->cursorInfoSelected = FALSE;
2377 zs->cursor.isSet = FALSE;
2378 zs->cursorHidden = FALSE;
2379 zs->pollHandle = 0;
2380
2381 WRAP (zs, s, preparePaintScreen, zoomPreparePaintScreen);
2382 WRAP (zs, s, donePaintScreen, zoomDonePaintScreen);
2383 WRAP (zs, s, paintOutput, zoomPaintOutput);
2384
2385 s->base.privates[zd->screenPrivateIndex].ptr = zs;
2386 return TRUE;
2387 }
2388
2389 static void
zoomFiniScreen(CompPlugin * p,CompScreen * s)2390 zoomFiniScreen (CompPlugin *p,
2391 CompScreen *s)
2392 {
2393 ZOOM_DISPLAY (s->display);
2394 ZOOM_SCREEN (s);
2395
2396 UNWRAP (zs, s, preparePaintScreen);
2397 UNWRAP (zs, s, donePaintScreen);
2398 UNWRAP (zs, s, paintOutput);
2399 if (zs->pollHandle)
2400 (*zd->mpFunc->removePositionPolling) (s, zs->pollHandle);
2401
2402 if (zs->zooms)
2403 free (zs->zooms);
2404
2405 damageScreen (s); // If we are unloaded and zoomed in.
2406 cursorZoomInactive (s);
2407 compFiniScreenOptions (s, zs->opt, SOPT_NUM);
2408 free (zs);
2409 }
2410
2411 static CompBool
zoomInitObject(CompPlugin * p,CompObject * o)2412 zoomInitObject (CompPlugin *p,
2413 CompObject *o)
2414 {
2415 static InitPluginObjectProc dispTab[] = {
2416 (InitPluginObjectProc) 0, /* InitCore */
2417 (InitPluginObjectProc) zoomInitDisplay,
2418 (InitPluginObjectProc) zoomInitScreen
2419 };
2420
2421 RETURN_DISPATCH (o, dispTab, ARRAY_SIZE (dispTab), TRUE, (p, o));
2422 }
2423
2424 static void
zoomFiniObject(CompPlugin * p,CompObject * o)2425 zoomFiniObject (CompPlugin *p,
2426 CompObject *o)
2427 {
2428 static FiniPluginObjectProc dispTab[] = {
2429 (FiniPluginObjectProc) 0, /* FiniCore */
2430 (FiniPluginObjectProc) zoomFiniDisplay,
2431 (FiniPluginObjectProc) zoomFiniScreen
2432 };
2433
2434 DISPATCH (o, dispTab, ARRAY_SIZE (dispTab), (p, o));
2435 }
2436
2437 static Bool
zoomInit(CompPlugin * p)2438 zoomInit (CompPlugin *p)
2439 {
2440 if (!compInitPluginMetadataFromInfo (&zoomMetadata,
2441 p->vTable->name,
2442 zoomDisplayOptionInfo,
2443 DOPT_NUM,
2444 zoomScreenOptionInfo,
2445 SOPT_NUM))
2446 return FALSE;
2447
2448 displayPrivateIndex = allocateDisplayPrivateIndex ();
2449 if (displayPrivateIndex < 0)
2450 {
2451 compFiniMetadata (&zoomMetadata);
2452 return FALSE;
2453 }
2454 compAddMetadataFromFile (&zoomMetadata, p->vTable->name);
2455 return TRUE;
2456 }
2457
2458 static CompOption *
zoomGetObjectOptions(CompPlugin * plugin,CompObject * object,int * count)2459 zoomGetObjectOptions (CompPlugin *plugin,
2460 CompObject *object,
2461 int *count)
2462 {
2463 static GetPluginObjectOptionsProc dispTab[] = {
2464 (GetPluginObjectOptionsProc) 0, /* GetCoreOptions */
2465 (GetPluginObjectOptionsProc) zoomGetDisplayOptions,
2466 (GetPluginObjectOptionsProc) zoomGetScreenOptions
2467 };
2468
2469 *count = 0;
2470
2471 RETURN_DISPATCH (object, dispTab, ARRAY_SIZE (dispTab),
2472 NULL, (plugin, object, count));
2473 }
2474
2475 static CompBool
zoomSetObjectOption(CompPlugin * plugin,CompObject * object,const char * name,CompOptionValue * value)2476 zoomSetObjectOption (CompPlugin *plugin,
2477 CompObject *object,
2478 const char *name,
2479 CompOptionValue *value)
2480 {
2481 static SetPluginObjectOptionProc dispTab[] = {
2482 (SetPluginObjectOptionProc) 0, /* SetCoreOption */
2483 (SetPluginObjectOptionProc) zoomSetDisplayOption,
2484 (SetPluginObjectOptionProc) zoomSetScreenOption
2485 };
2486
2487 RETURN_DISPATCH (object, dispTab, ARRAY_SIZE (dispTab), FALSE,
2488 (plugin, object, name, value));
2489 }
2490
2491 static void
zoomFini(CompPlugin * p)2492 zoomFini (CompPlugin *p)
2493 {
2494 freeDisplayPrivateIndex (displayPrivateIndex);
2495 compFiniMetadata (&zoomMetadata);
2496 }
2497
2498 static CompMetadata *
zoomGetMetadata(CompPlugin * plugin)2499 zoomGetMetadata (CompPlugin *plugin)
2500 {
2501 return &zoomMetadata;
2502 }
2503
2504 CompPluginVTable zoomVTable = {
2505 "ezoom",
2506 zoomGetMetadata,
2507 zoomInit,
2508 zoomFini,
2509 zoomInitObject,
2510 zoomFiniObject,
2511 zoomGetObjectOptions,
2512 zoomSetObjectOption
2513 };
2514
2515 CompPluginVTable *
getCompPluginInfo20070830(void)2516 getCompPluginInfo20070830 (void)
2517 {
2518 return &zoomVTable;
2519 }
2520