1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2
3 /**
4 * \file effects.c "Special effects" other than compositor effects.
5 *
6 * Before we had a serious compositor, we supported swooping
7 * rectangles for minimising and so on. These are still supported
8 * today, even when the compositor is enabled. The file contains two
9 * parts:
10 *
11 * 1) A set of functions, each of which implements a special effect.
12 * (Only the minimize function does anything interesting; we should
13 * probably get rid of the rest.)
14 *
15 * 2) A set of functions for moving a highlighted wireframe box around
16 * the screen, optionally with height and width shown in the middle.
17 * This is used for moving and resizing when reduced_resources is set.
18 *
19 * There was formerly a system which allowed callers to drop in their
20 * own handlers for various things; it was never used (people who want
21 * their own handlers can just modify this file, after all) and it added
22 * a good deal of extra complexity, so it has been removed. If you want it,
23 * it can be found in svn r3769.
24 *
25 * Once upon a time there were three different ways of drawing the box
26 * animation: window wireframe, window opaque, and root. People who had
27 * the shape extension theoretically had the choice of all three, and
28 * people who didn't weren't given the choice of the wireframe option.
29 * In practice, though, the opaque animation was never perfect, so it came
30 * down to the wireframe option for those who had the extension and
31 * the root option for those who didn't; there was actually no way of choosing
32 * any other option anyway. Work on the opaque animation stopped in 2002;
33 * anyone who wants something like that these days will be using the
34 * compositor anyway.
35 *
36 * In svn r3769 this was made explicit.
37 */
38
39 /*
40 * Copyright (C) 2001 Anders Carlsson, Havoc Pennington
41 *
42 * This program is free software; you can redistribute it and/or
43 * modify it under the terms of the GNU General Public License as
44 * published by the Free Software Foundation; either version 2 of the
45 * License, or (at your option) any later version.
46 *
47 * This program is distributed in the hope that it will be useful, but
48 * WITHOUT ANY WARRANTY; without even the implied warranty of
49 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
50 * General Public License for more details.
51 *
52 * You should have received a copy of the GNU General Public License
53 * along with this program; if not, write to the Free Software
54 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
55 * 02110-1301, USA.
56 */
57
58 #include <config.h>
59 #include "effects.h"
60 #include "display-private.h"
61 #include "ui.h"
62 #include "window-private.h"
63 #include "prefs.h"
64
65 #ifdef HAVE_SHAPE
66 #include <X11/extensions/shape.h>
67 #endif
68
69 #define META_MINIMIZE_ANIMATION_LENGTH 0.25
70 #define META_SHADE_ANIMATION_LENGTH 0.2
71
72 #include <string.h>
73
74 typedef struct MetaEffect MetaEffect;
75 typedef struct MetaEffectPriv MetaEffectPriv;
76
77 typedef struct
78 {
79 MetaScreen *screen;
80
81 double millisecs_duration;
82 gint64 start_time;
83
84 #ifdef HAVE_SHAPE
85 /** For wireframe window */
86 Window wireframe_xwindow;
87 #else
88 /** Rectangle to erase */
89 MetaRectangle last_rect;
90
91 /** First time we've plotted anything in this animation? */
92 gboolean first_time;
93
94 /** For wireframe drawn on root window */
95 GC gc;
96 #endif
97
98 MetaRectangle start_rect;
99 MetaRectangle end_rect;
100
101 } BoxAnimationContext;
102
103 /**
104 * Information we need to know during a maximise or minimise effect.
105 */
106 typedef struct
107 {
108 /** This is the normal-size window. */
109 MetaRectangle window_rect;
110 /** This is the size of the window when it's an icon. */
111 MetaRectangle icon_rect;
112 } MetaMinimizeEffect, MetaUnminimizeEffect;
113
114 struct MetaEffectPriv
115 {
116 MetaEffectFinished finished;
117 gpointer finished_data;
118 };
119
120 struct MetaEffect
121 {
122 /** The window the effect is applied to. */
123 MetaWindow *window;
124 /** Which effect is happening here. */
125 MetaEffectType type;
126 /** The effect handler can hang data here. */
127 gpointer info;
128
129 union
130 {
131 MetaMinimizeEffect minimize;
132 /* ... and theoretically anything else */
133 } u;
134
135 MetaEffectPriv *priv;
136 };
137
138 static void run_default_effect_handler (MetaEffect *effect);
139 static void run_handler (MetaEffect *effect);
140 static void effect_free (MetaEffect *effect);
141
142 static MetaEffect *
143 create_effect (MetaEffectType type,
144 MetaWindow *window,
145 MetaEffectFinished finished,
146 gpointer finished_data);
147
148 static void
149 draw_box_animation (MetaScreen *screen,
150 MetaRectangle *initial_rect,
151 MetaRectangle *destination_rect,
152 double seconds_duration);
153
154 /**
155 * Creates an effect.
156 *
157 */
158 static MetaEffect*
create_effect(MetaEffectType type,MetaWindow * window,MetaEffectFinished finished,gpointer finished_data)159 create_effect (MetaEffectType type,
160 MetaWindow *window,
161 MetaEffectFinished finished,
162 gpointer finished_data)
163 {
164 MetaEffect *effect = g_new (MetaEffect, 1);
165
166 effect->type = type;
167 effect->window = window;
168 effect->priv = g_new (MetaEffectPriv, 1);
169 effect->priv->finished = finished;
170 effect->priv->finished_data = finished_data;
171
172 return effect;
173 }
174
175 /**
176 * Destroys an effect. If the effect has a "finished" hook, it will be
177 * called before cleanup.
178 *
179 * \param effect The effect.
180 */
181 static void
effect_free(MetaEffect * effect)182 effect_free (MetaEffect *effect)
183 {
184 if (effect->priv->finished)
185 effect->priv->finished (effect->priv->finished_data);
186
187 g_free (effect->priv);
188 g_free (effect);
189 }
190
191 void
meta_effect_run_focus(MetaWindow * window,MetaEffectFinished finished,gpointer data)192 meta_effect_run_focus (MetaWindow *window,
193 MetaEffectFinished finished,
194 gpointer data)
195 {
196 MetaEffect *effect;
197
198 g_return_if_fail (window != NULL);
199
200 effect = create_effect (META_EFFECT_FOCUS, window, finished, data);
201
202 run_handler (effect);
203 }
204
205 void
meta_effect_run_minimize(MetaWindow * window,MetaRectangle * window_rect,MetaRectangle * icon_rect,MetaEffectFinished finished,gpointer data)206 meta_effect_run_minimize (MetaWindow *window,
207 MetaRectangle *window_rect,
208 MetaRectangle *icon_rect,
209 MetaEffectFinished finished,
210 gpointer data)
211 {
212 MetaEffect *effect;
213
214 g_return_if_fail (window != NULL);
215 g_return_if_fail (icon_rect != NULL);
216
217 effect = create_effect (META_EFFECT_MINIMIZE, window, finished, data);
218
219 effect->u.minimize.window_rect = *window_rect;
220 effect->u.minimize.icon_rect = *icon_rect;
221
222 run_handler (effect);
223 }
224
225 void
meta_effect_run_unminimize(MetaWindow * window,MetaRectangle * window_rect,MetaRectangle * icon_rect,MetaEffectFinished finished,gpointer data)226 meta_effect_run_unminimize (MetaWindow *window,
227 MetaRectangle *window_rect,
228 MetaRectangle *icon_rect,
229 MetaEffectFinished finished,
230 gpointer data)
231 {
232 MetaEffect *effect;
233
234 g_return_if_fail (window != NULL);
235 g_return_if_fail (icon_rect != NULL);
236
237 effect = create_effect (META_EFFECT_UNMINIMIZE, window, finished, data);
238
239 effect->u.minimize.window_rect = *window_rect;
240 effect->u.minimize.icon_rect = *icon_rect;
241
242 run_handler (effect);
243 }
244
245 void
meta_effect_run_close(MetaWindow * window,MetaEffectFinished finished,gpointer data)246 meta_effect_run_close (MetaWindow *window,
247 MetaEffectFinished finished,
248 gpointer data)
249 {
250 MetaEffect *effect;
251
252 g_return_if_fail (window != NULL);
253
254 effect = create_effect (META_EFFECT_CLOSE, window,
255 finished, data);
256
257 run_handler (effect);
258 }
259
260 /* old ugly minimization effect */
261
262 #ifdef HAVE_SHAPE
263 static void
update_wireframe_window(MetaDisplay * display,Window xwindow,const MetaRectangle * rect)264 update_wireframe_window (MetaDisplay *display,
265 Window xwindow,
266 const MetaRectangle *rect)
267 {
268 XMoveResizeWindow (display->xdisplay,
269 xwindow,
270 rect->x, rect->y,
271 rect->width, rect->height);
272
273 #define OUTLINE_WIDTH 3
274
275 if (rect->width > OUTLINE_WIDTH * 2 &&
276 rect->height > OUTLINE_WIDTH * 2)
277 {
278 XRectangle xrect;
279 Region inner_xregion;
280 Region outer_xregion;
281
282 inner_xregion = XCreateRegion ();
283 outer_xregion = XCreateRegion ();
284
285 xrect.x = 0;
286 xrect.y = 0;
287 xrect.width = rect->width;
288 xrect.height = rect->height;
289
290 XUnionRectWithRegion (&xrect, outer_xregion, outer_xregion);
291
292 xrect.x += OUTLINE_WIDTH;
293 xrect.y += OUTLINE_WIDTH;
294 xrect.width -= OUTLINE_WIDTH * 2;
295 xrect.height -= OUTLINE_WIDTH * 2;
296
297 XUnionRectWithRegion (&xrect, inner_xregion, inner_xregion);
298
299 XSubtractRegion (outer_xregion, inner_xregion, outer_xregion);
300
301 XShapeCombineRegion (display->xdisplay, xwindow,
302 ShapeBounding, 0, 0, outer_xregion, ShapeSet);
303
304 XDestroyRegion (outer_xregion);
305 XDestroyRegion (inner_xregion);
306 }
307 else
308 {
309 /* Unset the shape */
310 XShapeCombineMask (display->xdisplay, xwindow,
311 ShapeBounding, 0, 0, None, ShapeSet);
312 }
313 }
314 #endif
315
316 static gboolean
effects_draw_box_animation_timeout(BoxAnimationContext * context)317 effects_draw_box_animation_timeout (BoxAnimationContext *context)
318 {
319 double elapsed;
320 gint64 current_time;
321 MetaRectangle draw_rect;
322 double fraction;
323
324 #ifndef HAVE_SHAPE
325 if (!context->first_time)
326 {
327 /* Restore the previously drawn background */
328 XDrawRectangle (context->screen->display->xdisplay,
329 context->screen->xroot,
330 context->gc,
331 context->last_rect.x, context->last_rect.y,
332 context->last_rect.width, context->last_rect.height);
333 }
334 else
335 context->first_time = FALSE;
336
337 #endif /* !HAVE_SHAPE */
338
339 current_time = g_get_real_time ();
340
341 /* We use milliseconds for all times */
342 elapsed = (current_time - context->start_time) / 1000.0;
343
344 if (elapsed < 0)
345 {
346 /* Probably the system clock was set backwards? */
347 meta_warning ("System clock seemed to go backwards?\n");
348 elapsed = G_MAXDOUBLE; /* definitely done. */
349 }
350
351 if (elapsed > context->millisecs_duration)
352 {
353 /* All done */
354 #ifdef HAVE_SHAPE
355 XDestroyWindow (context->screen->display->xdisplay,
356 context->wireframe_xwindow);
357 #else
358 meta_display_ungrab (context->screen->display);
359 meta_ui_pop_delay_exposes (context->screen->ui);
360 XFreeGC (context->screen->display->xdisplay,
361 context->gc);
362 #endif /* !HAVE_SHAPE */
363
364 g_free (context);
365 return FALSE;
366 }
367
368 g_assert (context->millisecs_duration > 0.0);
369 fraction = elapsed / context->millisecs_duration;
370
371 draw_rect = context->start_rect;
372
373 /* Now add a delta proportional to elapsed time. */
374 draw_rect.x += (context->end_rect.x - context->start_rect.x) * fraction;
375 draw_rect.y += (context->end_rect.y - context->start_rect.y) * fraction;
376 draw_rect.width += (context->end_rect.width - context->start_rect.width) * fraction;
377 draw_rect.height += (context->end_rect.height - context->start_rect.height) * fraction;
378
379 /* don't confuse X or gdk-pixbuf with bogus rectangles */
380 if (draw_rect.width < 1)
381 draw_rect.width = 1;
382 if (draw_rect.height < 1)
383 draw_rect.height = 1;
384
385 #ifdef HAVE_SHAPE
386 update_wireframe_window (context->screen->display,
387 context->wireframe_xwindow,
388 &draw_rect);
389 #else
390 context->last_rect = draw_rect;
391
392 /* Draw the rectangle */
393 XDrawRectangle (context->screen->display->xdisplay,
394 context->screen->xroot,
395 context->gc,
396 draw_rect.x, draw_rect.y,
397 draw_rect.width, draw_rect.height);
398
399 #endif /* !HAVE_SHAPE */
400
401 /* kick changes onto the server */
402 XFlush (context->screen->display->xdisplay);
403
404 return TRUE;
405 }
406
407 void
draw_box_animation(MetaScreen * screen,MetaRectangle * initial_rect,MetaRectangle * destination_rect,double seconds_duration)408 draw_box_animation (MetaScreen *screen,
409 MetaRectangle *initial_rect,
410 MetaRectangle *destination_rect,
411 double seconds_duration)
412 {
413 BoxAnimationContext *context;
414
415 #ifdef HAVE_SHAPE
416 XSetWindowAttributes attrs;
417 #else
418 XGCValues gc_values;
419 #endif
420
421 g_return_if_fail (seconds_duration > 0.0);
422
423 if (g_getenv ("MARCO_DEBUG_EFFECTS"))
424 seconds_duration *= 10; /* slow things down */
425
426 /* Create the animation context */
427 context = g_new0 (BoxAnimationContext, 1);
428
429 context->screen = screen;
430
431 context->millisecs_duration = seconds_duration * 1000.0;
432
433 context->start_rect = *initial_rect;
434 context->end_rect = *destination_rect;
435
436 #ifdef HAVE_SHAPE
437
438 attrs.override_redirect = True;
439 attrs.background_pixel = BlackPixel (screen->display->xdisplay,
440 screen->number);
441
442 context->wireframe_xwindow = XCreateWindow (screen->display->xdisplay,
443 screen->xroot,
444 initial_rect->x,
445 initial_rect->y,
446 initial_rect->width,
447 initial_rect->height,
448 0,
449 CopyFromParent,
450 CopyFromParent,
451 (Visual *)CopyFromParent,
452 CWOverrideRedirect | CWBackPixel,
453 &attrs);
454
455 update_wireframe_window (screen->display,
456 context->wireframe_xwindow,
457 initial_rect);
458
459 XMapWindow (screen->display->xdisplay,
460 context->wireframe_xwindow);
461
462 #else /* !HAVE_SHAPE */
463
464 context->first_time = TRUE;
465 gc_values.subwindow_mode = IncludeInferiors;
466 gc_values.function = GXinvert;
467
468 context->gc = XCreateGC (screen->display->xdisplay,
469 screen->xroot,
470 GCSubwindowMode | GCFunction,
471 &gc_values);
472
473 /* Grab the X server to avoid screen dirt */
474 meta_display_grab (context->screen->display);
475 meta_ui_push_delay_exposes (context->screen->ui);
476 #endif
477
478 /* Do this only after we get the pixbuf from the server,
479 * so that the animation doesn't get truncated.
480 */
481 context->start_time = g_get_real_time ();
482
483 /* Add the timeout - a short one, could even use an idle,
484 * but this is maybe more CPU-friendly.
485 */
486 g_timeout_add (15,
487 (GSourceFunc)effects_draw_box_animation_timeout,
488 context);
489
490 /* kick changes onto the server */
491 XFlush (context->screen->display->xdisplay);
492 }
493
494 void
meta_effects_begin_wireframe(MetaScreen * screen,const MetaRectangle * rect,int width,int height)495 meta_effects_begin_wireframe (MetaScreen *screen,
496 const MetaRectangle *rect,
497 int width,
498 int height)
499 {
500 /* Grab the X server to avoid screen dirt */
501 meta_display_grab (screen->display);
502 meta_ui_push_delay_exposes (screen->ui);
503
504 meta_effects_update_wireframe (screen,
505 NULL, -1, -1,
506 rect, width, height);
507 }
508
509 static void
draw_xor_rect(MetaScreen * screen,const MetaRectangle * rect,int width,int height)510 draw_xor_rect (MetaScreen *screen,
511 const MetaRectangle *rect,
512 int width,
513 int height)
514 {
515 /* The lines in the center can't overlap the rectangle or each
516 * other, or the XOR gets reversed. So we have to draw things
517 * a bit oddly.
518 */
519 XSegment segments[8];
520 MetaRectangle shrunk_rect;
521 int i;
522
523 #define LINE_WIDTH META_WIREFRAME_XOR_LINE_WIDTH
524
525 /* We don't want the wireframe going outside the window area.
526 * It makes it harder for the user to position windows and it exposes other
527 * annoying bugs.
528 */
529 shrunk_rect = *rect;
530
531 shrunk_rect.x += LINE_WIDTH / 2 + LINE_WIDTH % 2;
532 shrunk_rect.y += LINE_WIDTH / 2 + LINE_WIDTH % 2;
533 shrunk_rect.width -= LINE_WIDTH + 2 * (LINE_WIDTH % 2);
534 shrunk_rect.height -= LINE_WIDTH + 2 * (LINE_WIDTH % 2);
535
536 XDrawRectangle (screen->display->xdisplay,
537 screen->xroot,
538 screen->root_xor_gc,
539 shrunk_rect.x, shrunk_rect.y,
540 shrunk_rect.width, shrunk_rect.height);
541
542 /* Don't put lines inside small rectangles where they won't fit */
543 if (shrunk_rect.width < (LINE_WIDTH * 4) ||
544 shrunk_rect.height < (LINE_WIDTH * 4))
545 return;
546
547 if ((width >= 0) && (height >= 0))
548 {
549 XGCValues gc_values = { 0 };
550
551 if (XGetGCValues (screen->display->xdisplay,
552 screen->root_xor_gc,
553 GCFont, &gc_values))
554 {
555 char *text;
556 int text_length;
557
558 XFontStruct *font_struct;
559 int text_width, text_height;
560 int box_x, box_y;
561 int box_width, box_height;
562
563 font_struct = XQueryFont (screen->display->xdisplay,
564 gc_values.font);
565
566 if (font_struct != NULL)
567 {
568 text = g_strdup_printf ("%d x %d", width, height);
569 text_length = strlen (text);
570
571 text_width = text_length * font_struct->max_bounds.width;
572 text_height = font_struct->max_bounds.descent +
573 font_struct->max_bounds.ascent;
574
575 box_width = text_width + 2 * LINE_WIDTH;
576 box_height = text_height + 2 * LINE_WIDTH;
577
578 box_x = shrunk_rect.x + (shrunk_rect.width - box_width) / 2;
579 box_y = shrunk_rect.y + (shrunk_rect.height - box_height) / 2;
580
581 if ((box_width < shrunk_rect.width) &&
582 (box_height < shrunk_rect.height))
583 {
584 XFillRectangle (screen->display->xdisplay,
585 screen->xroot,
586 screen->root_xor_gc,
587 box_x, box_y,
588 box_width, box_height);
589 XDrawString (screen->display->xdisplay,
590 screen->xroot,
591 screen->root_xor_gc,
592 box_x + LINE_WIDTH,
593 box_y + LINE_WIDTH + font_struct->max_bounds.ascent,
594 text, text_length);
595 }
596
597 g_free (text);
598
599 XFreeFontInfo (NULL, font_struct, 1);
600
601 if ((box_width + LINE_WIDTH) >= (shrunk_rect.width / 3))
602 return;
603
604 if ((box_height + LINE_WIDTH) >= (shrunk_rect.height / 3))
605 return;
606 }
607 }
608 }
609
610 /* Two vertical lines at 1/3 and 2/3 */
611 segments[0].x1 = shrunk_rect.x + shrunk_rect.width / 3;
612 segments[0].y1 = shrunk_rect.y + LINE_WIDTH / 2 + LINE_WIDTH % 2;
613 segments[0].x2 = segments[0].x1;
614 segments[0].y2 = shrunk_rect.y + shrunk_rect.height - LINE_WIDTH / 2;
615
616 segments[1] = segments[0];
617 segments[1].x1 = shrunk_rect.x + (shrunk_rect.width / 3) * 2;
618 segments[1].x2 = segments[1].x1;
619
620 /* Now make two horizontal lines at 1/3 and 2/3, but not
621 * overlapping the verticals
622 */
623
624 segments[2].x1 = shrunk_rect.x + LINE_WIDTH / 2 + LINE_WIDTH % 2;
625 segments[2].x2 = segments[0].x1 - LINE_WIDTH / 2;
626 segments[2].y1 = shrunk_rect.y + shrunk_rect.height / 3;
627 segments[2].y2 = segments[2].y1;
628
629 segments[3] = segments[2];
630 segments[3].x1 = segments[2].x2 + LINE_WIDTH;
631 segments[3].x2 = segments[1].x1 - LINE_WIDTH / 2;
632
633 segments[4] = segments[3];
634 segments[4].x1 = segments[3].x2 + LINE_WIDTH;
635 segments[4].x2 = shrunk_rect.x + shrunk_rect.width - LINE_WIDTH / 2;
636
637 /* Second horizontal line is just like the first, but
638 * shifted down
639 */
640 i = 5;
641 while (i < 8)
642 {
643 segments[i] = segments[i - 3];
644 segments[i].y1 = shrunk_rect.y + (shrunk_rect.height / 3) * 2;
645 segments[i].y2 = segments[i].y1;
646 ++i;
647 }
648
649 XDrawSegments (screen->display->xdisplay,
650 screen->xroot,
651 screen->root_xor_gc,
652 segments,
653 G_N_ELEMENTS (segments));
654 }
655
656 void
meta_effects_update_wireframe(MetaScreen * screen,const MetaRectangle * old_rect,int old_width,int old_height,const MetaRectangle * new_rect,int new_width,int new_height)657 meta_effects_update_wireframe (MetaScreen *screen,
658 const MetaRectangle *old_rect,
659 int old_width,
660 int old_height,
661 const MetaRectangle *new_rect,
662 int new_width,
663 int new_height)
664 {
665 if (old_rect)
666 draw_xor_rect (screen, old_rect, old_width, old_height);
667
668 if (new_rect)
669 draw_xor_rect (screen, new_rect, new_width, new_height);
670
671 XFlush (screen->display->xdisplay);
672 }
673
674 void
meta_effects_end_wireframe(MetaScreen * screen,const MetaRectangle * old_rect,int old_width,int old_height)675 meta_effects_end_wireframe (MetaScreen *screen,
676 const MetaRectangle *old_rect,
677 int old_width,
678 int old_height)
679 {
680 meta_effects_update_wireframe (screen,
681 old_rect, old_width, old_height,
682 NULL, -1, -1);
683
684 meta_display_ungrab (screen->display);
685 meta_ui_pop_delay_exposes (screen->ui);
686 }
687
688 static void
run_default_effect_handler(MetaEffect * effect)689 run_default_effect_handler (MetaEffect *effect)
690 {
691 switch (effect->type)
692 {
693 case META_EFFECT_MINIMIZE:
694 draw_box_animation (effect->window->screen,
695 &(effect->u.minimize.window_rect),
696 &(effect->u.minimize.icon_rect),
697 META_MINIMIZE_ANIMATION_LENGTH);
698 break;
699
700 default:
701 break;
702 }
703 }
704
705 static void
run_handler(MetaEffect * effect)706 run_handler (MetaEffect *effect)
707 {
708 if (meta_prefs_get_mate_animations ())
709 run_default_effect_handler (effect);
710
711 effect_free (effect);
712 }
713