1 /*
2 * Copyright �� 2005 Novell, Inc.
3 * Copyright (C) 2007, 2008,2010 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 * - Most features beyond basic zoom;
27 * Kristian Lyngstol <kristian@bohemians.org>
28 * - Original zoom plug-in; David Reveman <davidr@novell.com>
29 * - Original port to C++ by Sam Spilsbury <smspillaz@gmail.com>
30 *
31 * Description:
32 *
33 * This plug-in offers zoom functionality with focus tracking,
34 * fit-to-window actions, mouse panning, zoom area locking. Without
35 * disabling input.
36 *
37 * Note on actual zoom process
38 *
39 * The animation is done in preparePaintScreen, while instant movements
40 * are done by calling updateActualTranslate () after updating the
41 * translations. This causes [xyz]trans to be re-calculated. We keep track
42 * of each head separately.
43 *
44 * Note on input
45 *
46 * We can not redirect input yet, but this plug-in offers two fundamentally
47 * different approaches to achieve input enabled zoom:
48 *
49 * 1.
50 * Always have the zoomed area be in sync with the mouse cursor. This binds
51 * the zoom area to the mouse position at any given time. It allows using
52 * the original mouse cursor drawn by X, and is technically very safe.
53 * First used in Beryl's inputzoom.
54 *
55 * 2.
56 * Hide the real cursor and draw our own where it would be when zoomed in.
57 * This allows us to navigate with the mouse without constantly moving the
58 * zoom area. This is fairly close to what we want in the end when input
59 * redirection is available.
60 *
61 * This second method has one huge issue, which is bugged XFixes. After
62 * hiding the cursor once with XFixes, some mouse cursors will simply be
63 * invisible. The Firefox loading cursor being one of them.
64 *
65 * An other minor annoyance is that mouse sensitivity seems to increase as
66 * you zoom in, since the mouse isn't really zoomed at all.
67 *
68 * Todo:
69 * - Walk through C++ port and adjust comments for 2010.
70 * - See if anyone misses the filter setting
71 * - Verify XFixes fix... err.
72 * - Different multi head modes
73 */
74
75 #include "ezoom.h"
76
77 COMPIZ_PLUGIN_20090315 (ezoom, ZoomPluginVTable)
78
79
80 /*
81 * This toggles paint functions. We don't need to continually run code when we
82 * are not doing anything
83 */
84 static inline void
85 toggleFunctions (bool state)
86 {
87 ZOOM_SCREEN (screen);
88
89 screen->handleEventSetEnabled (zs, state);
90 zs->cScreen->preparePaintSetEnabled (zs, state);
91 zs->gScreen->glPaintOutputSetEnabled (zs, state);
92 zs->cScreen->donePaintSetEnabled (zs, state);
93 }
94
95 /* Check if the output is valid */
96 static inline bool
97 outputIsZoomArea (int out)
98 {
99 ZOOM_SCREEN (screen);
100
101 if (out < 0)
102 return false;
103 else if ((unsigned int) out >= zs->zooms.size ())
104 zs->zooms.resize (screen->outputDevs ().size ());
105 return true;
106 }
107
108 /* Check if zoom is active on the output specified */
109 static inline bool
110 isActive (int out)
111 {
112 ZOOM_SCREEN (screen);
113
114 if (!outputIsZoomArea (out))
115 return false;
116 if (zs->grabbed & (1 << zs->zooms.at (out).output))
117 return true;
118 return false;
119 }
120
121 /* Check if we are zoomed out and not going anywhere
122 * (similar to isActive but based on actual zoom, not grab)
123 */
124 static inline bool
125 isZoomed (int out)
126 {
127 ZOOM_SCREEN (screen);
128
129 if (!outputIsZoomArea (out))
130 return false;
131
132 if (zs->zooms.at (out).currentZoom != 1.0f
133 || zs->zooms.at (out).newZoom != 1.0f)
134 return true;
135
136 if (zs->zooms.at (out).zVelocity != 0.0f)
137 return true;
138
139 return false;
140 }
141
142 /* Returns the distance to the defined edge in zoomed pixels. */
143 int
144 EZoomScreen::distanceToEdge (int out, EZoomScreen::ZoomEdge edge)
145 {
146 int x1,y1,x2,y2;
147 CompOutput *o = &screen->outputDevs ().at (out);
148
149 if (!isActive (out))
150 return 0;
151 convertToZoomedTarget (out, o->region ()->extents.x2,
152 o->region ()->extents.y2, &x2, &y2);
153 convertToZoomedTarget (out, o->region ()->extents.x1,
154 o->region ()->extents.y1, &x1, &y1);
155 switch (edge)
156 {
157 case NORTH: return o->region ()->extents.y1 - y1;
158 case SOUTH: return y2 - o->region ()->extents.y2;
159 case EAST: return x2 - o->region ()->extents.x2;
160 case WEST: return o->region ()->extents.x1 - x1;
161 }
162 return 0; // Never reached.
163 }
164
165 /* Update/set translations based on zoom level and real translate. */
166 void
167 EZoomScreen::ZoomArea::updateActualTranslates ()
168 {
169 xtrans = -realXTranslate * (1.0f - currentZoom);
170 ytrans = realYTranslate * (1.0f - currentZoom);
171 }
172
173 /* Returns true if the head in question is currently moving.
174 * Since we don't always bother resetting everything when
175 * canceling zoom, we check for the condition of being completely
176 * zoomed out and not zooming in/out first.
177 */
178 bool
179 EZoomScreen::isInMovement (int out)
180 {
181 if (zooms.at (out).currentZoom == 1.0f &&
182 zooms.at (out).newZoom == 1.0f &&
183 zooms.at (out).zVelocity == 0.0f)
184 return false;
185 if (zooms.at (out).currentZoom != zooms.at (out).newZoom ||
186 zooms.at (out).xVelocity || zooms.at (out).yVelocity ||
187 zooms.at (out).zVelocity)
188 return true;
189 if (zooms.at (out).xTranslate != zooms.at (out).realXTranslate ||
190 zooms.at (out).yTranslate != zooms.at (out).realYTranslate)
191 return true;
192 return false;
193 }
194
195 /* Set the initial values of a zoom area. */
196 EZoomScreen::ZoomArea::ZoomArea (int out) :
197 output (out),
198 viewport (~0),
199 currentZoom (1.0f),
200 newZoom (1.0f),
201 xVelocity (0.0f),
202 yVelocity (0.0f),
203 zVelocity (0.0f),
204 xTranslate (0.0f),
205 yTranslate (0.0f),
206 realXTranslate (0.0f),
207 realYTranslate (0.0f),
208 locked (false)
209 {
210 updateActualTranslates ();
211 }
212
213 EZoomScreen::ZoomArea::ZoomArea () :
214 output (0),
215 viewport (~0),
216 currentZoom (1.0f),
217 newZoom (1.0f),
218 xVelocity (0.0f),
219 yVelocity (0.0f),
220 zVelocity (0.0f),
221 xTranslate (0.0f),
222 yTranslate (0.0f),
223 realXTranslate (0.0f),
224 realYTranslate (0.0f),
225 locked (false)
226 {
CID 12573 - UNINIT_CTOR
Non-static class member "xtrans" is not initialized in this constructor nor in any functions that it calls.
Non-static class member "ytrans" is not initialized in this constructor nor in any functions that it calls.
227 }
228 /* Adjust the velocity in the z-direction. */
229 void
230 EZoomScreen::adjustZoomVelocity (int out, float chunk)
231 {
232 float d, adjust, amount;
233
234 d = (zooms.at (out).newZoom - zooms.at (out).currentZoom) * 75.0f;
235
236 adjust = d * 0.002f;
237 amount = fabs (d);
238 if (amount < 1.0f)
239 amount = 1.0f;
240 else if (amount > 5.0f)
241 amount = 5.0f;
242
243 zooms.at (out).zVelocity =
244 (amount * zooms.at (out).zVelocity + adjust) / (amount + 1.0f);
245
246 if (fabs (d) < 0.1f && fabs (zooms.at (out).zVelocity) < 0.005f)
247 {
248 zooms.at (out).currentZoom = zooms.at (out).newZoom;
249 zooms.at (out).zVelocity = 0.0f;
250 }
251 else
252 {
253 zooms.at (out).currentZoom += (zooms.at (out).zVelocity * chunk) /
254 cScreen->redrawTime ();
255 }
256 }
257
258 /* Adjust the X/Y velocity based on target translation and real
259 * translation. */
260 void
261 EZoomScreen::adjustXYVelocity (int out, float chunk)
262 {
263 float xdiff, ydiff;
264 float xadjust, yadjust;
265 float xamount, yamount;
266
267 zooms.at (out).xVelocity /= 1.25f;
268 zooms.at (out).yVelocity /= 1.25f;
269 xdiff =
270 (zooms.at (out).xTranslate - zooms.at (out).realXTranslate) *
271 75.0f;
272 ydiff =
273 (zooms.at (out).yTranslate - zooms.at (out).realYTranslate) *
274 75.0f;
275 xadjust = xdiff * 0.002f;
276 yadjust = ydiff * 0.002f;
277 xamount = fabs (xdiff);
278 yamount = fabs (ydiff);
279
280 if (xamount < 1.0f)
281 xamount = 1.0f;
282 else if (xamount > 5.0)
283 xamount = 5.0f;
284
285 if (yamount < 1.0f)
286 yamount = 1.0f;
287 else if (yamount > 5.0)
288 yamount = 5.0f;
289
290 zooms.at (out).xVelocity =
291 (xamount * zooms.at (out).xVelocity + xadjust) / (xamount + 1.0f);
292 zooms.at (out).yVelocity =
293 (yamount * zooms.at (out).yVelocity + yadjust) / (yamount + 1.0f);
294
295 if ((fabs(xdiff) < 0.1f && fabs (zooms.at (out).xVelocity) < 0.005f) &&
296 (fabs(ydiff) < 0.1f && fabs (zooms.at (out).yVelocity) < 0.005f))
297 {
298 zooms.at (out).realXTranslate = zooms.at (out).xTranslate;
299 zooms.at (out).realYTranslate = zooms.at (out).yTranslate;
300 zooms.at (out).xVelocity = 0.0f;
301 zooms.at (out).yVelocity = 0.0f;
302 return;
303 }
304
305 zooms.at (out).realXTranslate +=
306 (zooms.at (out).xVelocity * chunk) / cScreen->redrawTime ();
307 zooms.at (out).realYTranslate +=
308 (zooms.at (out).yVelocity * chunk) / cScreen->redrawTime ();
309 }
310
311 /* Animate the movement (if any) in preparation of a paint screen. */
312 void
313 EZoomScreen::preparePaint (int msSinceLastPaint)
314 {
315 if (grabbed)
316 {
317 int steps;
318 float amount, chunk;
319
320 amount = msSinceLastPaint * 0.05f * optionGetSpeed ();
321 steps = amount / (0.5f * optionGetTimestep ());
322 if (!steps)
323 steps = 1;
324 chunk = amount / (float) steps;
325 while (steps--)
326 {
327 unsigned int out;
328 for (out = 0; out < zooms.size (); out++)
329 {
330 if (!isInMovement (out) || !isActive (out))
331 continue;
332
333 adjustXYVelocity (out, chunk);
334 adjustZoomVelocity (out, chunk);
335 zooms.at (out).updateActualTranslates ();
336 if (!isZoomed (out))
337 {
338 zooms.at (out).xVelocity = zooms.at (out).yVelocity =
339 0.0f;
340 grabbed &= ~(1 << zooms.at (out).output);
341 if (!grabbed)
342 {
343 cScreen->damageScreen ();
344 toggleFunctions (false);
345 }
346 }
347 }
348 }
349 if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse)
350 syncCenterToMouse ();
351 }
352
353 cScreen->preparePaint (msSinceLastPaint);
354 }
355
356 /* Damage screen if we're still moving. */
357 void
358 EZoomScreen::donePaint ()
359 {
360 if (grabbed)
361 {
362 unsigned int out;
363 for (out = 0; out < zooms.size (); out++)
364 {
365 if (isInMovement (out) && isActive (out))
366 {
367 cScreen->damageScreen ();
368 break;
369 }
370 }
371 }
372 else if (grabIndex)
373 cScreen->damageScreen ();
374 else
375 toggleFunctions (false);
376
377 cScreen->donePaint ();
378 }
379 /* Draws a box from the screen coordinates inx1,iny1 to inx2,iny2 */
380 void
381 EZoomScreen::drawBox (const GLMatrix &transform,
382 CompOutput *output,
383 CompRect box)
384 {
385 GLMatrix zTransform (transform);
386 int x1,x2,y1,y2;
387 int inx1, inx2, iny1, iny2;
388 int out = output->id ();
389 GLushort colorData[4];
390 GLfloat vertexData[12];
391 GLVertexBuffer *streamingBuffer = GLVertexBuffer::streamingBuffer ();
392
393 zTransform.toScreenSpace (output, -DEFAULT_Z_CAMERA);
394 convertToZoomed (out, box.x1 (), box.y1 (), &inx1, &iny1);
395 convertToZoomed (out, box.x2 (), box.y2 (), &inx2, &iny2);
396
397 x1 = MIN (inx1, inx2);
398 y1 = MIN (iny1, iny2);
399 x2 = MAX (inx1, inx2);
400 y2 = MAX (iny1, iny2);
401
402 streamingBuffer->begin (GL_TRIANGLE_STRIP);
403
404 colorData[0] = 0x2fff;
405 colorData[1] = 0x2fff;
406 colorData[2] = 0x2fff;
407 colorData[3] = 0x4fff;
408
409 streamingBuffer->addColors (1, colorData);
410
411 vertexData[0] = x1;
412 vertexData[1] = y1;
413 vertexData[2] = 0.0f;
414 vertexData[3] = x1;
415 vertexData[4] = y2;
416 vertexData[5] = 0.0f;
417 vertexData[6] = x2;
418 vertexData[7] = y1;
419 vertexData[8] = 0.0f;
420 vertexData[9] = x2;
421 vertexData[10] = y2;
422 vertexData[11] = 0.0f;
423
424 streamingBuffer->addVertices (4, vertexData);
425
426 streamingBuffer->end ();
427 streamingBuffer->render (zTransform);
428
429
430 streamingBuffer->begin (GL_LINE_LOOP);
431
432 colorData[0] = 0x2fff;
433 colorData[1] = 0x2fff;
434 colorData[2] = 0x4fff;
435 colorData[3] = 0x9fff;
436
437 streamingBuffer->addColors (1, colorData);
438
439 vertexData[0] = x1;
440 vertexData[1] = y1;
441 vertexData[2] = 0.0f;
442 vertexData[3] = x2;
443 vertexData[4] = y1;
444 vertexData[5] = 0.0f;
445 vertexData[6] = x2;
446 vertexData[7] = y2;
447 vertexData[8] = 0.0f;
448 vertexData[9] = x1;
449 vertexData[10] = y2;
450 vertexData[11] = 0.0f;
451
452 streamingBuffer->addVertices (4, vertexData);
453
454 streamingBuffer->end ();
455 streamingBuffer->render (zTransform);
456 }
457 /* Apply the zoom if we are grabbed.
458 * Make sure to use the correct filter.
459 */
460 bool
461 EZoomScreen::glPaintOutput (const GLScreenPaintAttrib &attrib,
462 const GLMatrix &transform,
463 const CompRegion ®ion,
464 CompOutput *output,
465 unsigned int mask)
466 {
467 bool status;
468 int out = output->id ();
469
470 if (isActive (out))
471 {
472 GLScreenPaintAttrib sa = attrib;
473 GLMatrix zTransform = transform;
474
475 mask &= ~PAINT_SCREEN_REGION_MASK;
476 mask |= PAINT_SCREEN_CLEAR_MASK;
477
478 zTransform.scale (1.0f / zooms.at (out).currentZoom,
479 1.0f / zooms.at (out).currentZoom,
480 1.0f);
481 zTransform.translate (zooms.at (out).xtrans,
482 zooms.at (out).ytrans,
483 0);
484
485 mask |= PAINT_SCREEN_TRANSFORMED_MASK;
486
487 status = gScreen->glPaintOutput (sa, zTransform, region, output, mask);
488
489 drawCursor (output, transform);
490
491 }
492 else
493 {
494 status = gScreen->glPaintOutput (attrib, transform, region, output,
495 mask);
496 }
497 if (grabIndex)
498 drawBox (transform, output, box);
499
500 return status;
501 }
502
503 /* Makes sure we're not attempting to translate too far.
504 * We are restricted to 0.5 to not go beyond the end
505 * of the screen/head. */
506 static inline void
507 constrainZoomTranslate ()
508 {
509 unsigned int out;
510 ZOOM_SCREEN (screen);
511
512 for (out = 0; out < zs->zooms.size (); out++)
513 {
514 if (zs->zooms.at (out).xTranslate > 0.5f)
515 zs->zooms.at (out).xTranslate = 0.5f;
516 else if (zs->zooms.at (out).xTranslate < -0.5f)
517 zs->zooms.at (out).xTranslate = -0.5f;
518
519 if (zs->zooms.at (out).yTranslate > 0.5f)
520 zs->zooms.at (out).yTranslate = 0.5f;
521 else if (zs->zooms.at (out).yTranslate < -0.5f)
522 zs->zooms.at (out).yTranslate = -0.5f;
523 }
524 }
525
526 /* Functions for adjusting the zoomed area.
527 * These are the core of the zoom plug-in; Anything wanting
528 * to adjust the zoomed area must use setCenter or setZoomArea
529 * and setScale or front ends to them. */
530
531 /* Sets the center of the zoom area to X,Y.
532 * We have to be able to warp the pointer here: If we are moved by
533 * anything except mouse movement, we have to sync the
534 * mouse pointer. This is to allow input, and is NOT necessary
535 * when input redirection is available to us or if we're cheating
536 * and using a scaled mouse cursor to imitate IR.
537 * The center is not the center of the screen. This is the target-center;
538 * that is, it's the point that's the same regardless of zoom level.
539 */
540 void
541 EZoomScreen::setCenter (int x, int y, bool instant)
542 {
543 int out = screen->outputDeviceForPoint (x, y);
544 CompOutput *o = &screen->outputDevs ().at (out);
545
546 if (zooms.at (out).locked)
547 return;
548
549 zooms.at (out).xTranslate = (float)
550 ((x - o->x1 ()) - o->width () / 2) / (o->width ());
551 zooms.at (out).yTranslate = (float)
552 ((y - o->y1 ()) - o->height () / 2) / (o->height ());
553
554 if (instant)
555 {
556 zooms.at (out).realXTranslate = zooms.at (out).xTranslate;
557 zooms.at (out).realYTranslate = zooms.at (out).yTranslate;
558 zooms.at (out).yVelocity = 0.0f;
559 zooms.at (out).xVelocity = 0.0f;
560 zooms.at (out).updateActualTranslates ();
561 }
562
563 if (optionGetZoomMode () == EzoomOptions::ZoomModePanArea)
564 restrainCursor (out);
565 }
566
567 /* Zooms the area described.
568 * The math could probably be cleaned up, but should be correct now. */
569 void
570 EZoomScreen::setZoomArea (int x,
571 int y,
572 int width,
573 int height,
574 bool instant)
575 {
576 CompWindow::Geometry outGeometry (x, y, width, height, 0);
577 int out = screen->outputDeviceForGeometry (outGeometry);
578 CompOutput *o = &screen->outputDevs ().at (out);
579
580 if (zooms.at (out).newZoom == 1.0f)
581 return;
582
583 if (zooms.at (out).locked)
584 return;
585 zooms.at (out).xTranslate =
586 (float) -((o->width () / 2) - (x + (width / 2) - o->x1 ()))
587 / (o->width ());
588 zooms.at (out).xTranslate /= (1.0f - zooms.at (out).newZoom);
589 zooms.at (out).yTranslate =
590 (float) -((o->height () / 2) - (y + (height / 2) - o->y1 ()))
591 / (o->height ());
592 zooms.at (out).yTranslate /= (1.0f - zooms.at (out).newZoom);
593 constrainZoomTranslate ();
594
595 if (instant)
596 {
597 zooms.at (out).realXTranslate = zooms.at (out).xTranslate;
598 zooms.at (out).realYTranslate = zooms.at (out).yTranslate;
599 zooms.at (out).updateActualTranslates ();
600 }
601
602 if (optionGetZoomMode () == EzoomOptions::ZoomModePanArea)
603 restrainCursor (out);
604 }
605
606 /* Moves the zoom area to the window specified */
607 void
608 EZoomScreen::areaToWindow (CompWindow *w)
609 {
610 int left = w->serverX () - w->border ().left;
611 int width = w->width () + w->border ().left + w->border ().right;
612 int top = w->serverY () - w->border ().top;
613 int height = w->height () + w->border ().top + w->border ().bottom;
614
615 setZoomArea (left, top, width, height, false);
616 }
617
618 /* Pans the zoomed area vertically/horizontally by * value * zs->panFactor
619 * TODO: Fix output. */
620 void
621 EZoomScreen::panZoom (int xvalue, int yvalue)
622 {
623 unsigned int out;
624
625 for (out = 0; out < zooms.size (); out++)
626 {
627 zooms.at (out).xTranslate +=
628 optionGetPanFactor () * xvalue *
629 zooms.at (out).currentZoom;
630 zooms.at (out).yTranslate +=
631 optionGetPanFactor () * yvalue *
632 zooms.at (out).currentZoom;
633 }
634
635 constrainZoomTranslate ();
636 }
637
638 /* Enables polling of mouse position, and refreshes currently
639 * stored values.
640 */
641 void
642 EZoomScreen::enableMousePolling ()
643 {
644 pollHandle.start ();
645 lastChange = time(NULL);
646 mouse = MousePoller::getCurrentPosition ();
647 }
648
649 /* Sets the zoom (or scale) level.
650 * Cleans up if we are suddenly zoomed out.
651 */
652 void
653 EZoomScreen::setScale (int out, float value)
654 {
655 if (zooms.at (out).locked)
656 return;
657
658 if (value >= 1.0f)
659 value = 1.0f;
660 else
661 {
662 if (!pollHandle.active ())
663 enableMousePolling ();
664 grabbed |= (1 << zooms.at (out).output);
665 cursorZoomActive (out);
666 }
667
668 if (value == 1.0f)
669 {
670 zooms.at (out).xTranslate = 0.0f;
671 zooms.at (out).yTranslate = 0.0f;
672 cursorZoomInactive ();
673 }
674
675 if (value < optionGetMinimumZoom ())
676 value = optionGetMinimumZoom ();
677
678 zooms.at (out).newZoom = value;
679 cScreen->damageScreen();
680 }
681
682 /* Sets the zoom factor to the bigger of the two floats supplied.
683 * Convenience function for setting the scale factor for an area.
684 */
685 static inline void
686 setScaleBigger (int out, float x, float y)
687 {
688 ZOOM_SCREEN (screen);
689 zs->setScale (out, x > y ? x : y);
690 }
691
692 /* Mouse code...
693 * This takes care of keeping the mouse in sync with the zoomed area and
694 * vice versa.
695 * See heading for description.
696 */
697
698 /* Syncs the center, based on translations, back to the mouse.
699 * This should be called when doing non-IR zooming and moving the zoom
700 * area based on events other than mouse movement.
701 */
702 void
703 EZoomScreen::syncCenterToMouse ()
704 {
705 int x, y;
706 int out;
707 CompOutput *o;
708
709 out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
710 o = &screen->outputDevs ().at (out);
711
712 if (!isInMovement (out))
713 return;
714
715 x = (int) ((zooms.at (out).realXTranslate * o->width ()) +
716 (o->width () / 2) + o->x1 ());
717 y = (int) ((zooms.at (out).realYTranslate * o->height ()) +
718 (o->height () / 2) + o->y1 ());
719
720 if ((x != mouse.x () || y != mouse.y ())
721 && grabbed && zooms.at (out).newZoom != 1.0f)
722 {
723 screen->warpPointer (x - pointerX , y - pointerY );
724 mouse.setX (x);
725 mouse.setY (y);
726 }
727 }
728
729 /* Convert the point X,Y to where it would be when zoomed. */
730 void
731 EZoomScreen::convertToZoomed (int out,
732 int x,
733 int y,
734 int *resultX,
735 int *resultY)
736 {
737 CompOutput *o;
738
739 if (!outputIsZoomArea (out))
740 {
741 *resultX = x;
742 *resultY = y;
743 }
744
745 o = &screen->outputDevs ()[out];
746 ZoomArea &za = zooms.at (out);
747
748 x -= o->x1 ();
749 y -= o->y1 ();
750 *resultX = x - (za.realXTranslate *
751 (1.0f - za.currentZoom) * o->width ()) - o->width () / 2;
752 *resultX /= za.currentZoom;
753 *resultX += o->width () / 2;
754 *resultX += o->x1 ();
755 *resultY = y - (za.realYTranslate *
756 (1.0f - za.currentZoom) * o->height ()) - o->height ()/ 2;
757 *resultY /= za.currentZoom;
758 *resultY += o->height ()/ 2;
759 *resultY += o->y1 ();
760 }
761
762 /* Same but use targeted translation, not real */
763 void
764 EZoomScreen::convertToZoomedTarget (int out,
765 int x,
766 int y,
767 int *resultX,
768 int *resultY)
769 {
770 CompOutput *o = &screen->outputDevs ().at (out);
771
772 if (!outputIsZoomArea (out))
773 {
774 *resultX = x;
775 *resultY = y;
776 }
777
778 ZoomArea &za = zooms.at (out);
779
780 x -= o->x1 ();
781 y -= o->y1 ();
782 *resultX = x - (za.xTranslate *
783 (1.0f - za.newZoom) * o->width ()) - o->width () / 2;
784 *resultX /= za.newZoom;
785 *resultX += o->width () / 2;
786 *resultX += o->x1 ();
787 *resultY = y - (za.yTranslate *
788 (1.0f - za.newZoom) * o->height ()) - o->height ()/2;
789 *resultY /= za.newZoom;
790 *resultY += o->height () / 2;
791 *resultY += o->y1 ();
792 }
793
794 /* Make sure the given point + margin is visible;
795 * Translate to make it visible if necessary.
796 * Returns false if the point isn't on a actively zoomed head
797 * or the area is locked. */
798 bool
799 EZoomScreen::ensureVisibility (int x, int y, int margin)
800 {
801 int zoomX, zoomY;
802 int out;
803 CompOutput *o;
804
805 out = screen->outputDeviceForPoint (x, y);
806 if (!isActive (out))
807 return false;
808
809 o = &screen->outputDevs ().at (out);
810 convertToZoomedTarget (out, x, y, &zoomX, &zoomY);
811 ZoomArea &za = zooms.at (out);
812 if (za.locked)
813 return false;
814
815 #define FACTOR (za.newZoom / (1.0f - za.newZoom))
816 if (zoomX + margin > o->x2 ())
817 za.xTranslate +=
818 (FACTOR * (float) (zoomX + margin - o->x2 ())) /
819 (float) o->width ();
820 else if (zoomX - margin < o->x1 ())
821 za.xTranslate +=
822 (FACTOR * (float) (zoomX - margin - o->x1 ())) /
823 (float) o->width ();
824
825 if (zoomY + margin > o->y2 ())
826 za.yTranslate +=
827 (FACTOR * (float) (zoomY + margin - o->y2 ())) /
828 (float) o->height ();
829 else if (zoomY - margin < o->y1 ())
830 za.yTranslate +=
831 (FACTOR * (float) (zoomY - margin - o->y1 ())) /
832 (float) o->height ();
833 #undef FACTOR
834 constrainZoomTranslate ();
835 return true;
836 }
837
838 /* Attempt to ensure the visibility of an area defined by x1/y1 and x2/y2.
839 * See ensureVisibility () for details.
840 *
841 * This attempts to find the translations that leaves the biggest part of
842 * the area visible.
843 *
844 * gravity defines what part of the window that should get
845 * priority if it isn't possible to fit all of it.
846 */
847 void
848 EZoomScreen::ensureVisibilityArea (int x1,
849 int y1,
850 int x2,
851 int y2,
852 int margin,
853 ZoomGravity gravity)
854 {
855 int targetX, targetY, targetW, targetH;
856 int out;
857 CompOutput *o;
858
859 out = screen->outputDeviceForPoint (x1 + (x2-x1/2), y1 + (y2-y1/2));
860 o = &screen->outputDevs ().at (out);
861
862 #define WIDTHOK (float)(x2-x1) / (float)o->width () < zooms.at (out).newZoom
863 #define HEIGHTOK (float)(y2-y1) / (float)o->height () < zooms.at (out).newZoom
864
865 if (WIDTHOK &&
866 HEIGHTOK) {
867 ensureVisibility (x1, y1, margin);
868 ensureVisibility (x2, y2, margin);
869 return;
870 }
871
872 switch (gravity)
873 {
874 case NORTHWEST:
875 targetX = x1;
876 targetY = y1;
877 if (WIDTHOK)
878 targetW = x2 - x1;
879 else
880 targetW = o->width () * zooms.at (out).newZoom;
881 if (HEIGHTOK)
882 targetH = y2 - y1;
883 else
884 targetH = o->height () * zooms.at (out).newZoom;
885 break;
886 case NORTHEAST:
887 targetY = y1;
888 if (WIDTHOK)
889 {
890 targetX = x1;
891 targetW = x2-x1;
892 }
893 else
894 {
895 targetX = x2 - o->width () * zooms.at (out).newZoom;
896 targetW = o->width () * zooms.at (out).newZoom;
897 }
898
899 if (HEIGHTOK)
900 targetH = y2-y1;
901 else
902 targetH = o->height () * zooms.at (out).newZoom;
903 break;
904 case SOUTHWEST:
905 targetX = x1;
906 if (WIDTHOK)
907 targetW = x2-x1;
908 else
909 targetW = o->width () * zooms.at (out).newZoom;
910 if (HEIGHTOK)
911 {
912 targetY = y1;
913 targetH = y2-y1;
914 }
915 else
916 {
917 targetY = y2 - (o->width () * zooms.at (out).newZoom);
918 targetH = o->width () * zooms.at (out).newZoom;
919 }
920 break;
921 case SOUTHEAST:
922 if (WIDTHOK)
923 {
924 targetX = x1;
925 targetW = x2-x1;
926 }
927 else
928 {
929 targetW = o->width () * zooms.at (out).newZoom;
930 targetX = x2 - targetW;
931 }
932
933 if (HEIGHTOK)
934 {
935 targetY = y1;
936 targetH = y2 - y1;
937 }
938 else
939 {
940 targetH = o->height () * zooms.at (out).newZoom;
941 targetY = y2 - targetH;
942 }
943 break;
944 case CENTER:
945 default:
946 setCenter (x1 + (x2 - x1 / 2), y1 + (y2 - y1 / 2), false);
947 return;
948 break;
949 }
950
951 setZoomArea (targetX, targetY, targetW, targetH, false);
952 return ;
953 }
954
955 /* Ensures that the cursor is visible on the given head.
956 * Note that we check if currentZoom is 1.0f, because that often means that
957 * mouseX and mouseY is not up-to-date (since the polling timer just
958 * started).
959 */
960 void
961 EZoomScreen::restrainCursor (int out)
962 {
963 int x1, y1, x2, y2, margin;
964 int diffX = 0, diffY = 0;
965 int north, south, east, west;
966 float z;
967 CompOutput *o = &screen->outputDevs ().at (out);
968
969 z = zooms.at (out).newZoom;
970 margin = optionGetRestrainMargin ();
971 north = distanceToEdge (out, NORTH);
972 south = distanceToEdge (out, SOUTH);
973 east = distanceToEdge (out, EAST);
974 west = distanceToEdge (out, WEST);
975
976 if (zooms.at (out).currentZoom == 1.0f)
977 {
978 lastChange = time(NULL);
979 mouse = MousePoller::getCurrentPosition ();
980 }
981
982 convertToZoomedTarget (out, mouse.x () - cursor.hotX,
983 mouse.y () - cursor.hotY, &x1, &y1);
984 convertToZoomedTarget
985 (out,
986 mouse.x () - cursor.hotX + cursor.width,
987 mouse.y () - cursor.hotY + cursor.height,
988 &x2, &y2);
989
990 if ((x2 - x1 > o->x2 () - o->x1 ()) ||
991 (y2 - y1 > o->y2 () - o->y1 ()))
992 return;
993 if (x2 > o->x2 () - margin && east > 0)
994 diffX = x2 - o->x2 () + margin;
995 else if (x1 < o->x1 () + margin && west > 0)
996 diffX = x1 - o->x1 () - margin;
997
998 if (y2 > o->y2 () - margin && south > 0)
999 diffY = y2 - o->y2 () + margin;
1000 else if (y1 < o->y1 () + margin && north > 0)
1001 diffY = y1 - o->y1 () - margin;
1002
1003 if (abs(diffX)*z > 0 || abs(diffY)*z > 0)
1004 screen->warpPointer ((int) (mouse.x () - pointerX) -
1005 (int) ((float)diffX * z),
1006 (int) (mouse.y () - pointerY) -
1007 (int) ((float)diffY * z));
1008 }
1009
1010 /* Check if the cursor is still visible.
1011 * We also make sure to activate/deactivate cursor scaling here
1012 * so we turn on/off the pointer if it moves from one head to another.
1013 * FIXME: Detect an actual output change instead of spamming.
1014 * FIXME: The second ensureVisibility (sync with restrain).
1015 */
1016 void
1017 EZoomScreen::cursorMoved ()
1018 {
1019 int out;
1020
1021 out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
1022 if (isActive (out))
1023 {
1024 if (optionGetRestrainMouse ())
1025 restrainCursor (out);
1026
1027 if (optionGetZoomMode () == EzoomOptions::ZoomModePanArea)
1028 {
1029 ensureVisibilityArea (mouse.x () - cursor.hotX,
1030 mouse.y () - cursor.hotY,
1031 mouse.x () + cursor.width -
1032 cursor.hotX,
1033 mouse.y () + cursor.height -
1034 cursor.hotY,
1035 optionGetRestrainMargin (),
1036 NORTHWEST);
1037 }
1038
1039 cursorZoomActive (out);
1040 }
1041 else
1042 {
1043 cursorZoomInactive ();
1044 }
1045 }
1046
1047 /* Update the mouse position.
1048 * Based on the zoom engine in use, we will have to move the zoom area.
1049 * This might have to be added to a timer.
1050 */
1051 void
1052 EZoomScreen::updateMousePosition (const CompPoint &p)
1053 {
1054 int out;
1055 mouse.setX (p.x ());
1056 mouse.setY (p.y ());
1057 out = screen->outputDeviceForPoint (mouse.x (), mouse.y ());
1058 lastChange = time(NULL);
1059 if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse &&
1060 !isInMovement (out))
1061 setCenter (mouse.x (), mouse.y (), true);
1062 cursorMoved ();
1063 cScreen->damageScreen ();
1064 }
1065
1066 /* Timeout handler to poll the mouse. Returns false (and thereby does not
1067 * get re-added to the queue) when zoom is not active. */
1068 void
1069 EZoomScreen::updateMouseInterval (const CompPoint &p)
1070 {
1071 updateMousePosition (p);
1072
1073 if (!grabbed)
1074 {
1075 cursorMoved ();
1076 if (pollHandle.active ())
1077 pollHandle.stop ();
1078 }
1079 }
1080
1081 /* Free a cursor */
1082 void
1083 EZoomScreen::freeCursor (CursorTexture * cursor)
1084 {
1085 if (!cursor->isSet)
1086 return;
1087
1088 cursor->isSet = false;
1089 glDeleteTextures (1, &cursor->texture);
1090 cursor->texture = 0;
1091 }
1092
1093 /* Translate into place and draw the scaled cursor. */
1094 void
1095 EZoomScreen::drawCursor (CompOutput *output,
1096 const GLMatrix &transform)
1097 {
1098 int out = output->id ();
1099
1100 if (cursor.isSet)
1101 {
1102 GLMatrix sTransform = transform;
1103 float scaleFactor;
1104 int ax, ay, x, y;
1105 GLfloat textureData[8];
1106 GLfloat vertexData[12];
1107 GLVertexBuffer *streamingBuffer = GLVertexBuffer::streamingBuffer ();
1108
1109 /*
1110 * XXX: expo knows how to handle mouse when zoomed, so we back off
1111 * when expo is active.
1112 */
1113 if (screen->grabExist ( "expo"))
1114 {
1115 cursorZoomInactive ();
1116 return;
1117 }
1118
1119 sTransform.toScreenSpace (output, -DEFAULT_Z_CAMERA);
1120 convertToZoomed (out, mouse.x (), mouse.y (), &ax, &ay);
1121 sTransform.translate ((float) ax, (float) ay, 0.0f);
1122
1123 if (optionGetScaleMouseDynamic ())
1124 scaleFactor = 1.0f / zooms.at (out).currentZoom;
1125 else
1126 scaleFactor = 1.0f / optionGetScaleMouseStatic ();
1127
1128 sTransform.scale (scaleFactor, scaleFactor, 1.0f);
1129 x = -cursor.hotX;
1130 y = -cursor.hotY;
1131
1132 glEnable (GL_BLEND);
1133 glEnable (GL_TEXTURE_2D);
1134 glBindTexture (GL_TEXTURE_2D, cursor.texture);
1135
1136 streamingBuffer->begin (GL_TRIANGLE_STRIP);
1137
1138 vertexData[0] = x;
1139 vertexData[1] = y;
1140 vertexData[2] = 0.0f;
1141 vertexData[3] = x;
1142 vertexData[4] = y + cursor.height;
1143 vertexData[5] = 0.0f;
1144 vertexData[6] = x + cursor.width;
1145 vertexData[7] = y;
1146 vertexData[8] = 0.0f;
1147 vertexData[9] = x + cursor.width;
1148 vertexData[10] = y + cursor.height;
1149 vertexData[11] = 0.0f;
1150
1151 streamingBuffer->addVertices (4, vertexData);
1152
1153 textureData[0] = 0;
1154 textureData[1] = 0;
1155 textureData[2] = 0;
1156 textureData[3] = 1;
1157 textureData[4] = 1;
1158 textureData[5] = 0;
1159 textureData[6] = 1;
1160 textureData[7] = 1;
1161
1162 streamingBuffer->addTexCoords (0, 4, textureData);
1163
1164 streamingBuffer->end ();
1165 streamingBuffer->render (sTransform);
1166
1167 glBindTexture (GL_TEXTURE_2D, 0);
1168 glDisable (GL_TEXTURE_2D);
1169 glDisable (GL_BLEND);
1170 }
1171 }
1172
1173 /* Create (if necessary) a texture to store the cursor,
1174 * fetch the cursor with XFixes. Store it. */
1175 void
1176 EZoomScreen::updateCursor (CursorTexture * cursor)
1177 {
1178 unsigned char *pixels;
1179 int i;
1180 Display *dpy = screen->dpy ();
1181
1182 if (!cursor->isSet)
1183 {
1184 cursor->isSet = true;
1185 cursor->screen = screen;
1186
1187 glEnable (GL_TEXTURE_2D);
1188 glGenTextures (1, &cursor->texture);
1189 glBindTexture (GL_TEXTURE_2D, cursor->texture);
1190
1191 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1192 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1193 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
1194 gScreen->textureFilter ());
1195 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
1196 gScreen->textureFilter ());
1197 }
1198 else {
1199 glEnable (GL_TEXTURE_2D);
1200 }
1201
1202 XFixesCursorImage *ci = XFixesGetCursorImage (dpy);
1203
1204 if (ci)
1205 {
1206 cursor->width = ci->width;
1207 cursor->height = ci->height;
1208 cursor->hotX = ci->xhot;
1209 cursor->hotY = ci->yhot;
1210 pixels = (unsigned char *) malloc (ci->width * ci->height * 4);
1211
1212 if (!pixels)
1213 {
1214 XFree (ci);
1215 return;
1216 }
1217
1218 for (i = 0; i < ci->width * ci->height; i++)
1219 {
1220 unsigned long pix = ci->pixels[i];
1221 pixels[i * 4] = pix & 0xff;
1222 pixels[(i * 4) + 1] = (pix >> 8) & 0xff;
1223 pixels[(i * 4) + 2] = (pix >> 16) & 0xff;
1224 pixels[(i * 4) + 3] = (pix >> 24) & 0xff;
1225 }
1226
1227 XFree (ci);
1228 }
1229 else
1230 {
1231 /* Fallback R: 255 G: 255 B: 255 A: 255
1232 * FIXME: Draw a cairo mouse cursor */
1233
1234 cursor->width = 1;
1235 cursor->height = 1;
1236 cursor->hotX = 0;
1237 cursor->hotY = 0;
1238 pixels = (unsigned char *) malloc (cursor->width * cursor->height * 4);
1239
1240 if (!pixels)
1241 return;
1242
1243 for (i = 0; i < cursor->width * cursor->height; i++)
1244 {
1245 unsigned long pix = 0x00ffffff;
1246 pixels[i * 4] = pix & 0xff;
1247 pixels[(i * 4) + 1] = (pix >> 8) & 0xff;
1248 pixels[(i * 4) + 2] = (pix >> 16) & 0xff;
1249 pixels[(i * 4) + 3] = (pix >> 24) & 0xff;
1250 }
1251
1252 compLogMessage ("ezoom", CompLogLevelWarn, "unable to get system cursor image!");
1253 }
1254
1255 glBindTexture (GL_TEXTURE_2D, cursor->texture);
1256 glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, cursor->width,
1257 cursor->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
1258 glBindTexture (GL_TEXTURE_2D, 0);
1259 glDisable (GL_TEXTURE_2D);
1260
1261 free (pixels);
1262 }
1263
1264 /* We are no longer zooming the cursor, so display it. */
1265 void
1266 EZoomScreen::cursorZoomInactive ()
1267 {
1268 if (!fixesSupported)
1269 return;
1270
1271 if (cursorInfoSelected)
1272 {
1273 cursorInfoSelected = false;
1274 XFixesSelectCursorInput (screen->dpy (), screen->root (), 0);
1275 }
1276
1277 if (cursor.isSet)
1278 {
1279 freeCursor (&cursor);
1280 }
1281
1282 if (cursorHidden)
1283 {
1284 cursorHidden = false;
1285 XFixesShowCursor (screen->dpy (), screen->root ());
1286 }
1287 }
1288
1289 /* Cursor zoom is active: We need to hide the original,
1290 * register for Cursor notifies and display the new one.
1291 * This can be called multiple times, not just on initial
1292 * activation.
1293 */
1294 void
1295 EZoomScreen::cursorZoomActive (int out)
1296 {
1297 if (!fixesSupported)
1298 return;
1299
1300 /* Force cursor hiding and mouse panning if this output is locked
1301 * and cursor hiding is not enabled and we are syncing the mouse
1302 */
1303
1304 if (!optionGetScaleMouse () &&
1305 (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse &&
1306 optionGetHideOriginalMouse () &&
1307 !zooms.at (out).locked))
1308 return;
1309
1310 if (!cursorInfoSelected)
1311 {
1312 cursorInfoSelected = true;
1313 XFixesSelectCursorInput (screen->dpy (), screen->root (),
1314 XFixesDisplayCursorNotifyMask);
1315 updateCursor (&cursor);
1316 }
1317 if (canHideCursor && !cursorHidden &&
1318 (optionGetHideOriginalMouse () ||
1319 zooms.at (out).locked))
1320 {
1321 cursorHidden = true;
1322 XFixesHideCursor (screen->dpy (), screen->root ());
1323 }
1324 }
1325
1326 /* Set the zoom area
1327 * This is an interface for scripting.
1328 * int32:x1: left x coordinate
1329 * int32:y1: top y coordinate
1330 * int32:x2: right x
1331 * int32:y2: bottom y
1332 * x2 and y2 can be omitted to assume x1==x2+1 y1==y2+1
1333 * boolean:scale: True if we should modify the zoom level, false to just
1334 * adjust the movement/translation.
1335 * boolean:restrain: True to warp the pointer so it's visible.
1336 */
1337 bool
1338 EZoomScreen::setZoomAreaAction (CompAction *action,
1339 CompAction::State state,
1340 CompOption::Vector options)
1341 {
1342 int x1, y1, x2, y2, out;
1343 bool scale, restrain;
1344 CompOutput *o;
1345
1346 x1 = CompOption::getIntOptionNamed (options, "x1", -1);
1347 y1 = CompOption::getIntOptionNamed (options, "y1", -1);
1348 x2 = CompOption::getIntOptionNamed (options, "x2", -1);
1349 y2 = CompOption::getIntOptionNamed (options, "y2", -1);
1350 scale = CompOption::getBoolOptionNamed (options, "scale", false);
1351 restrain = CompOption::getBoolOptionNamed (options, "restrain", false);
1352
1353 if (x1 < 0 || y1 < 0)
1354 return false;
1355
1356 if (x2 < 0)
1357 x2 = x1 + 1;
1358
1359 if (y2 < 0)
1360 y2 = y1 + 1;
1361
1362 out = screen->outputDeviceForPoint (x1, y1);
1363 #define WIDTH (x2 - x1)
1364 #define HEIGHT (y2 - y1)
1365 setZoomArea (x1, y1, WIDTH, HEIGHT, false);
1366 o = &screen->outputDevs (). at(out);
1367 if (scale && WIDTH && HEIGHT)
1368 setScaleBigger (out, (float) WIDTH / o->width (),
1369 (float) HEIGHT / o->height ());
1370 #undef WIDTH
1371 #undef HEIGHT
1372 if (restrain)
1373 restrainCursor (out);
1374
1375 toggleFunctions (true);
1376
1377 return true;
1378 }
1379
1380 /* Ensure visibility of an area defined by x1->x2/y1->y2
1381 * int:x1: left X coordinate
1382 * int:x2: right X Coordinate
1383 * int:y1: top Y coordinate
1384 * int:y2: bottom Y coordinate
1385 * bool:scale: zoom out if necessary to ensure visibility
1386 * bool:restrain: Restrain the mouse cursor
1387 * int:margin: The margin to use (default: 0)
1388 * if x2/y2 is omitted, it is ignored.
1389 */
1390 bool
1391 EZoomScreen::ensureVisibilityAction (CompAction *action,
1392 CompAction::State state,
1393 CompOption::Vector options)
1394 {
1395 int x1, y1, x2, y2, margin, out;
1396 bool scale, restrain;
1397 CompOutput *o;
1398
1399 x1 = CompOption::getIntOptionNamed (options, "x1", -1);
1400 y1 = CompOption::getIntOptionNamed (options, "y1", -1);
1401 x2 = CompOption::getIntOptionNamed (options, "x2", -1);
1402 y2 = CompOption::getIntOptionNamed (options, "y2", -1);
1403 margin = CompOption::getBoolOptionNamed (options, "margin", 0);
1404 scale = CompOption::getBoolOptionNamed (options, "scale", false);
1405 restrain = CompOption::getBoolOptionNamed (options, "restrain", false);
1406 if (x1 < 0 || y1 < 0)
1407 return false;
1408 if (x2 < 0)
1409 y2 = y1 + 1;
1410 out = screen->outputDeviceForPoint (x1, y1);
1411 ensureVisibility (x1, y1, margin);
1412 if (x2 >= 0 && y2 >= 0)
1413 ensureVisibility (x2, y2, margin);
1414 o = &screen->outputDevs (). at(out);
1415 #define WIDTH (x2 - x1)
1416 #define HEIGHT (y2 - y1)
1417 if (scale && WIDTH && HEIGHT)
1418 setScaleBigger (out, (float) WIDTH / o->width (),
1419 (float) HEIGHT / o->height ());
1420 #undef WIDTH
1421 #undef HEIGHT
1422 if (restrain)
1423 restrainCursor (out);
1424
1425 toggleFunctions (true);
1426
1427 return true;
1428 }
1429
1430 /* Finished here */
1431
1432 bool
1433 EZoomScreen::zoomBoxActivate (CompAction *action,
1434 CompAction::State state,
1435 CompOption::Vector options)
1436 {
1437 grabIndex = screen->pushGrab (None, "ezoom");
1438 clickPos.setX (pointerX);
1439 clickPos.setY (pointerY);
1440 box.setGeometry (pointerX, pointerY, 0, 0);
1441 if (state & CompAction::StateInitButton)
1442 action->setState (action->state () | CompAction::StateTermButton);
1443
1444 toggleFunctions (true);
1445
1446 return true;
1447 }
1448
1449 bool
1450 EZoomScreen::zoomBoxDeactivate (CompAction *action,
1451 CompAction::State state,
1452 CompOption::Vector options)
1453 {
1454 if (grabIndex)
1455 {
1456 int out;
1457 int x, y, width, height;
1458 CompOutput *o;
1459
1460 screen->removeGrab (grabIndex, NULL);
1461 grabIndex = 0;
1462
1463 if (pointerX < clickPos.x ())
1464 {
1465 box.setX (pointerX);
1466 box.setWidth (clickPos.x () - pointerX);
1467 }
1468 else
1469 {
1470 box.setWidth (pointerX - clickPos.x ());
1471 }
1472
1473 if (pointerY < clickPos.y ())
1474 {
1475 box.setY (pointerY);
1476 box.setHeight (clickPos.y () - pointerY);
1477 }
1478 else
1479 {
1480 box.setHeight (pointerY - clickPos.y ());
1481 }
1482
1483 x = MIN (box.x1 (), box.x2 ());
1484 y = MIN (box.y1 (), box.y2 ());
1485 width = MAX (box.x1 (), box.x2 ()) - x;
1486 height = MAX (box.y1 (), box.y2 ()) - y;
1487
1488 CompWindow::Geometry outGeometry (x, y, width, height, 0);
1489
1490 out = screen->outputDeviceForGeometry (outGeometry);
1491 o = &screen->outputDevs (). at (out);
1492 setScaleBigger (out, (float) width/o->width (), (float)
1493 height/o->height ());
1494 setZoomArea (x,y,width,height,false);
1495 }
1496
1497 toggleFunctions (true);
1498
1499 return true;
1500 }
1501
1502 /* Zoom in to the area pointed to by the mouse.
1503 */
1504 bool
1505 EZoomScreen::zoomIn (CompAction *action,
1506 CompAction::State state,
1507 CompOption::Vector options)
1508 {
1509 int out = screen->outputDeviceForPoint (pointerX, pointerY);
1510
1511 if (optionGetZoomMode () == EzoomOptions::ZoomModeSyncMouse &&
1512 !isInMovement (out))
1513 setCenter (pointerX, pointerY, true);
1514
1515 setScale (out,
1516 zooms.at (out).newZoom /
1517 optionGetZoomFactor ());
1518
1519 toggleFunctions (true);
1520
1521 return true;
1522 }
1523
1524 /* Locks down the current zoom area
1525 */
1526 bool
1527 EZoomScreen::lockZoomAction (CompAction *action,
1528 CompAction::State state,
1529 CompOption::Vector options)
1530 {
1531 int out = screen->outputDeviceForPoint (pointerX, pointerY);
1532 zooms.at (out).locked = !zooms.at (out).locked;
1533
1534 return true;
1535 }
1536
1537 /* Zoom to a specific level.
1538 * target defines the target zoom level.
1539 * First set the scale level and mark the display as grabbed internally (to
1540 * catch the FocusIn event). Either target the focused window or the mouse,
1541 * depending on settings.
1542 * FIXME: A bit of a mess...
1543 */
1544 bool
1545 EZoomScreen::zoomSpecific (CompAction *action,
1546 CompAction::State state,
1547 CompOption::Vector options,
1548 SpecificZoomTarget target)
1549 {
1550 int out = screen->outputDeviceForPoint (pointerX, pointerY);
1551 float zoom_level;
1552 CompWindow *w;
1553
1554 switch (target)
1555 {
1556 case ZoomTargetFirst:
1557 zoom_level = optionGetZoomSpec1 ();
1558 break;
1559 case ZoomTargetSecond:
1560 zoom_level = optionGetZoomSpec2 ();
1561 break;
1562 case ZoomTargetThird:
1563 zoom_level = optionGetZoomSpec3 ();
1564 break;
1565 default:
1566 return false;
1567 }
1568
1569 if (zoom_level == 1.0f && zooms.at (out).newZoom == 1.0f)
1570 return false;
1571 if (screen->otherGrabExist (NULL))
1572 return false;
1573
1574 setScale (out, zoom_level);
1575
1576 w = screen->findWindow (screen->activeWindow ());
1577 if (optionGetSpecTargetFocus ()
1578 && w)
1579 {
1580 areaToWindow (w);
1581 }
1582 else
1583 {
1584 int x = CompOption::getIntOptionNamed (options, "x", 0);
1585 int y = CompOption::getIntOptionNamed (options, "y", 0);
1586 setCenter (x, y, false);
1587 }
1588
1589 toggleFunctions (true);
1590
1591 return true;
1592 }
1593
1594 /* TODO: Add specific zoom boost::bind's */
1595
1596 /* Zooms to fit the active window to the screen without cutting
1597 * it off and targets it.
1598 */
1599 bool
1600 EZoomScreen::zoomToWindow (CompAction *action,
1601 CompAction::State state,
1602 CompOption::Vector options)
1603 {
1604 int width, height, out;
1605 Window xid;
1606 CompWindow *w;
1607 CompOutput *o;
1608
1609 xid = CompOption::getIntOptionNamed (options, "window", 0);
1610 w = screen->findWindow (xid);
1611 if (!w)
1612 return true;
1613 width = w->width () + w->border ().left + w->border ().right;
1614 height = w->height () + w->border ().top + w->border ().bottom;
1615 out = screen->outputDeviceForGeometry (w->geometry ());
1616 o = &screen->outputDevs ().at (out);
1617 setScaleBigger (out, (float) width/o->width (),
1618 (float) height/o->height ());
1619 areaToWindow (w);
1620 toggleFunctions (true);
1621
1622 return true;
1623 }
1624
1625 bool
1626 EZoomScreen::zoomPan (CompAction *action,
1627 CompAction::State state,
1628 CompOption::Vector options,
1629 float horizAmount,
1630 float vertAmount)
1631 {
1632 panZoom (horizAmount, vertAmount);
1633
1634 return true;
1635 }
1636
1637 /* Centers the mouse based on zoom level and translation.
1638 */
1639 bool
1640 EZoomScreen::zoomCenterMouse (CompAction *action,
1641 CompAction::State state,
1642 CompOption::Vector options)
1643 {
1644 int out;
1645
1646
1647 out = screen->outputDeviceForPoint (pointerX, pointerY);
1648 screen->warpPointer ((int) (screen->outputDevs ().at (out).width ()/2 +
1649 screen->outputDevs ().at (out).x1 () - pointerX)
1650 + ((float) screen->outputDevs ().at (out).width () *
1651 -zooms.at (out).xtrans),
1652 (int) (screen->outputDevs ().at (out).height ()/2 +
1653 screen->outputDevs ().at (out).y1 () - pointerY)
1654 + ((float) screen->outputDevs ().at (out).height () *
1655 zooms.at (out).ytrans));
1656 return true;
1657 }
1658
1659 /* Resize a window to fit the zoomed area.
1660 * This could probably do with some moving-stuff too.
1661 * IE: Move the zoom area afterwards. And ensure
1662 * the window isn't resized off-screen.
1663 */
1664 bool
1665 EZoomScreen::zoomFitWindowToZoom (CompAction *action,
1666 CompAction::State state,
1667 CompOption::Vector options)
1668 {
1669 int out;
1670 unsigned int mask = CWWidth | CWHeight;
1671 XWindowChanges xwc;
1672 CompWindow *w;
1673
1674 w = screen->findWindow (CompOption::getIntOptionNamed (
1675 options, "window", 0));
1676 if (!w)
1677 return true;
1678
1679 out = screen->outputDeviceForGeometry (w->geometry ());
1680 xwc.x = w->serverX ();
1681 xwc.y = w->serverY ();
1682 xwc.width = (int) (screen->outputDevs ().at (out).width () *
1683 zooms.at (out).currentZoom -
1684 (int) ((w->border ().left + w->border ().right)));
1685 xwc.height = (int) (screen->outputDevs ().at (out).height () *
1686 zooms.at (out).currentZoom -
1687 (int) ((w->border ().top + w->border ().bottom)));
1688
1689 w->constrainNewWindowSize (xwc.width,
1690 xwc.height,
1691 &xwc.width,
1692 &xwc.height);
1693
1694
1695 if (xwc.width == w->serverWidth ())
1696 mask &= ~CWWidth;
1697
1698 if (xwc.height == w->serverHeight ())
1699 mask &= ~CWHeight;
1700
1701 if (w->mapNum () && (mask & (CWWidth | CWHeight)))
1702 w->sendSyncRequest ();
1703
1704 w->configureXWindow (mask, &xwc);
1705
1706 toggleFunctions (true);
1707
1708 return true;
1709 }
1710
1711 bool
1712 EZoomScreen::initiate (CompAction *action,
1713 CompAction::State state,
1714 CompOption::Vector options)
1715 {
1716 zoomIn (action, state, options);
1717
1718 if (state & CompAction::StateInitKey)
1719 action->setState (action->state () | CompAction::StateTermKey);
1720
1721 if (state & CompAction::StateInitButton)
1722 action->setState (action->state () | CompAction::StateTermButton);
1723
1724 toggleFunctions (true);
1725
1726 return true;
1727 }
1728
1729 bool
1730 EZoomScreen::zoomOut (CompAction *action,
1731 CompAction::State state,
1732 CompOption::Vector options)
1733 {
1734 int out = screen->outputDeviceForPoint (pointerX, pointerY);
1735
1736 setScale (out,
1737 zooms.at (out).newZoom *
1738 optionGetZoomFactor ());
1739
1740 toggleFunctions (true);
1741
1742 return true;
1743 }
1744
1745 bool
1746 EZoomScreen::terminate (CompAction *action,
1747 CompAction::State state,
1748 CompOption::Vector options)
1749 {
1750 int out;
1751
1752 out = screen->outputDeviceForPoint (pointerX, pointerY);
1753
1754 if (grabbed)
1755 {
1756 zooms.at (out).newZoom = 1.0f;
1757 cScreen->damageScreen ();
1758 }
1759
1760 toggleFunctions (true);
1761
1762 action->setState (action->state () & ~(CompAction::StateTermKey |
1763 CompAction::StateTermButton));
1764 return false;
1765
1766 }
1767
1768 /* Focus-track related event handling.
1769 * The lastMapped is a hack to ensure that newly mapped windows are
1770 * caught even if the grab that (possibly) triggered them affected
1771 * the mode. Windows created by a key binding (like creating a terminal
1772 * on a key binding) tends to trigger FocusIn events with mode other than
1773 * Normal. This works around this problem.
1774 * FIXME: Cleanup.
1775 * TODO: Avoid maximized windows.
1776 */
1777 void
1778 EZoomScreen::focusTrack (XEvent *event)
1779 {
1780 int out;
1781 static Window lastMapped = 0;
1782
1783 CompWindow *w;
1784
1785 if (event->type == MapNotify)
1786 {
1787 lastMapped = event->xmap.window;
1788 return;
1789 }
1790 else if (event->type != FocusIn)
1791 return;
1792
1793 if ((event->xfocus.mode != NotifyNormal)
1794 && (lastMapped != event->xfocus.window))
1795 return;
1796
1797 lastMapped = 0;
1798 w = screen->findWindow (event->xfocus.window);
1799 if (w == NULL || w->id () == screen->activeWindow ())
1800 return;
1801
1802 if (time(NULL) - lastChange < optionGetFollowFocusDelay () ||
1803 !optionGetFollowFocus ())
1804 return;
1805
1806 out = screen->outputDeviceForGeometry (w->geometry ());
1807 if (!isActive (out) &&
1808 !optionGetAlwaysFocusFitWindow ())
1809 return;
1810 if (optionGetFocusFitWindow ())
1811 {
1812 int width = w->width () + w->border ().left + w->border ().right;
1813 int height = w->height () + w->border ().top + w->border ().bottom;
1814 float scale = MAX ((float) width/screen->outputDevs ().at(out).width (),
1815 (float) height/screen->outputDevs ().at (out).height ());
1816 if (scale > optionGetAutoscaleMin ())
1817 setScale (out, scale);
1818 }
1819
1820 areaToWindow (w);
1821
1822 toggleFunctions (true);
1823 }
1824
1825
1826 /* Event handler. Pass focus-related events on and handle XFixes events. */
1827 void
1828 EZoomScreen::handleEvent (XEvent *event)
1829 {
1830 switch (event->type) {
1831 case MotionNotify:
1832 if (grabIndex)
1833 {
1834 if (pointerX < clickPos.x ())
1835 {
1836 box.setX (pointerX);
1837 box.setWidth (clickPos.x () - pointerX);
1838 }
1839 else
1840 {
1841 box.setWidth (pointerX - clickPos.x ());
1842 }
1843
1844 if (pointerY < clickPos.y ())
1845 {
1846 box.setY (pointerY);
1847 box.setHeight (clickPos.y () - pointerY);
1848 }
1849 else
1850 {
1851 box.setHeight (pointerY - clickPos.y ());
1852 }
1853 cScreen->damageScreen ();
1854 }
1855 break;
1856
1857 case FocusIn:
1858 case MapNotify:
1859 focusTrack (event);
1860 break;
1861 default:
1862 if (event->type == fixesEventBase + XFixesCursorNotify)
1863 {
1864 //XFixesCursorNotifyEvent *cev = (XFixesCursorNotifyEvent *)
1865 //event;
1866 if (cursor.isSet)
1867 updateCursor (&cursor);
1868 }
1869 break;
1870 }
1871
1872 screen->handleEvent (event);
1873 }
1874
1875 /* TODO: Use this ctor carefully */
1876
1877 EZoomScreen::CursorTexture::CursorTexture () :
1878 isSet (false),
1879 screen (0),
1880 width (0),
1881 height (0),
1882 hotX (0),
1883 hotY (0)
1884 {
1885 }
1886
1887 EZoomScreen::EZoomScreen (CompScreen *screen) :
1888 PluginClassHandler <EZoomScreen, CompScreen> (screen),
1889 cScreen (CompositeScreen::get (screen)),
1890 gScreen (GLScreen::get (screen)),
1891 grabbed (0),
1892 grabIndex (0),
1893 lastChange (0),
1894 cursorInfoSelected (false),
1895 cursorHidden (false)
1896 {
1897 ScreenInterface::setHandler (screen, false);
1898 CompositeScreenInterface::setHandler (cScreen, false);
1899 GLScreenInterface::setHandler (gScreen, false);
1900
1901 int major, minor;
1902 unsigned int n;
1903 fixesSupported =
1904 XFixesQueryExtension(screen->dpy (),
1905 &fixesEventBase,
1906 &fixesErrorBase);
1907
1908 XFixesQueryVersion (screen->dpy (), &major, &minor);
1909
1910 if (major >= 4)
1911 canHideCursor = true;
1912 else
1913 canHideCursor = false;
1914
1915 n = screen->outputDevs ().size ();
1916
1917 for (unsigned int i = 0; i < n; i++)
1918 {
1919 /* zs->grabbed is a mask ... Thus this limit */
1920 if (i > sizeof (long int) * 8)
1921 break;
1922 ZoomArea za (i);
1923 zooms.push_back (za);
1924 }
1925
1926 pollHandle.setCallback (boost::bind (
1927 &EZoomScreen::updateMouseInterval, this, _1));
1928
1929 optionSetZoomInButtonInitiate (boost::bind (&EZoomScreen::zoomIn, this, _1,
1930 _2, _3));
1931 optionSetZoomOutButtonInitiate (boost::bind (&EZoomScreen::zoomOut, this, _1,
1932 _2, _3));
1933 optionSetZoomInKeyInitiate (boost::bind (&EZoomScreen::zoomIn, this, _1,
1934 _2, _3));
1935 optionSetZoomOutKeyInitiate (boost::bind (&EZoomScreen::zoomOut, this, _1,
1936 _2, _3));
1937
1938 optionSetZoomSpecific1KeyInitiate (boost::bind (&EZoomScreen::zoomSpecific,
1939 this, _1, _2, _3,
1940 ZoomTargetFirst));
1941 optionSetZoomSpecific2KeyInitiate (boost::bind (&EZoomScreen::zoomSpecific,
1942 this, _1, _2, _3,
1943 ZoomTargetSecond));
1944 optionSetZoomSpecific3KeyInitiate (boost::bind (&EZoomScreen::zoomSpecific,
1945 this, _1, _2, _3,
1946 ZoomTargetThird));
1947
1948 optionSetPanLeftKeyInitiate (boost::bind (&EZoomScreen::zoomPan, this, _1,
1949 _2, _3, -1, 0));
1950 optionSetPanRightKeyInitiate (boost::bind (&EZoomScreen::zoomPan, this, _1,
1951 _2, _3, 1, 0));
1952 optionSetPanUpKeyInitiate (boost::bind (&EZoomScreen::zoomPan, this, _1, _2,
1953 _3, 0, -1));
1954 optionSetPanDownKeyInitiate (boost::bind (&EZoomScreen::zoomPan, this, _1,
1955 _2, _3, 0, 1));
1956
1957 optionSetFitToWindowKeyInitiate (boost::bind (&EZoomScreen::zoomToWindow,
1958 this, _1, _2, _3));
1959 optionSetCenterMouseKeyInitiate (boost::bind (&EZoomScreen::zoomCenterMouse,
1960 this, _1, _2, _3));
1961 optionSetFitToZoomKeyInitiate (boost::bind (
1962 &EZoomScreen::zoomFitWindowToZoom, this,
1963 _1, _2, _3));
1964
1965
1966 optionSetLockZoomKeyInitiate (boost::bind (&EZoomScreen::lockZoomAction,
1967 this, _1, _2, _3));
1968 optionSetZoomBoxButtonInitiate (boost::bind (&EZoomScreen::zoomBoxActivate,
1969 this, _1, _2, _3));
1970 optionSetZoomBoxButtonTerminate (boost::bind (
1971 &EZoomScreen::zoomBoxDeactivate, this,
1972 _1, _2, _3));
1973 optionSetSetZoomAreaInitiate (boost::bind (
1974 &EZoomScreen::setZoomAreaAction, this,
1975 _1, _2, _3));
1976 optionSetEnsureVisibilityInitiate (boost::bind (
1977 &EZoomScreen::ensureVisibilityAction, this,
1978 _1, _2, _3));
1979
1980 }
1981
1982 EZoomScreen::~EZoomScreen ()
1983 {
1984 if (pollHandle.active ())
1985 pollHandle.stop ();
1986
1987 if (zooms.size ())
1988 zooms.clear ();
1989
1990 cScreen->damageScreen ();
1991 cursorZoomInactive ();
1992 }
1993
1994 bool
1995 ZoomPluginVTable::init ()
1996 {
1997 if (!CompPlugin::checkPluginABI ("core", CORE_ABIVERSION) ||
1998 !CompPlugin::checkPluginABI ("composite", COMPIZ_COMPOSITE_ABI) ||
1999 !CompPlugin::checkPluginABI ("opengl", COMPIZ_OPENGL_ABI) ||
2000 !CompPlugin::checkPluginABI ("mousepoll", COMPIZ_MOUSEPOLL_ABI))
2001 return false;
2002
2003 return true;
2004 }