1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2
3 /* Metacity window frame manager widget */
4
5 /*
6 * Copyright (C) 2001 Havoc Pennington
7 * Copyright (C) 2003 Red Hat, Inc.
8 * Copyright (C) 2005, 2006 Elijah Newren
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of the
13 * License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "config.h"
25
26 #include <cairo-xlib.h>
27 #include <math.h>
28 #include <string.h>
29
30 #include "core/frame.h"
31 #include "core/window-private.h"
32 #include "meta/boxes.h"
33 #include "meta/prefs.h"
34 #include "meta/theme.h"
35 #include "meta/util.h"
36 #include "ui/ui.h"
37 #include "ui/frames.h"
38 #include "x11/meta-x11-window-control.h"
39 #include "x11/window-x11-private.h"
40 #include "x11/window-x11.h"
41
42 #define DEFAULT_INNER_BUTTON_BORDER 3
43
44 static void meta_frames_destroy (GtkWidget *object);
45 static void meta_frames_finalize (GObject *object);
46 static void meta_frames_style_updated (GtkWidget *widget);
47
48 static gboolean meta_frames_draw (GtkWidget *widget,
49 cairo_t *cr);
50
51 static void meta_ui_frame_attach_style (MetaUIFrame *frame);
52
53 static void meta_ui_frame_paint (MetaUIFrame *frame,
54 cairo_t *cr);
55
56 static void meta_ui_frame_calc_geometry (MetaUIFrame *frame,
57 MetaFrameGeometry *fgeom);
58
59 static void meta_ui_frame_update_prelit_control (MetaUIFrame *frame,
60 MetaFrameControl control);
61
62 static void meta_frames_font_changed (MetaFrames *frames);
63 static void meta_frames_button_layout_changed (MetaFrames *frames);
64
65
66 static GdkRectangle* control_rect (MetaFrameControl control,
67 MetaFrameGeometry *fgeom);
68 static MetaFrameControl get_control (MetaUIFrame *frame,
69 int x,
70 int y);
71
72 G_DEFINE_TYPE (MetaFrames, meta_frames, GTK_TYPE_WINDOW);
73
74 enum
75 {
76 META_ACTION_CLICK,
77 META_ACTION_RIGHT_CLICK,
78 META_ACTION_MIDDLE_CLICK,
79 META_ACTION_DOUBLE_CLICK,
80 META_ACTION_IGNORE
81 };
82
83 static GObject *
meta_frames_constructor(GType gtype,guint n_properties,GObjectConstructParam * properties)84 meta_frames_constructor (GType gtype,
85 guint n_properties,
86 GObjectConstructParam *properties)
87 {
88 GObject *object;
89 GObjectClass *gobject_class;
90
91 gobject_class = G_OBJECT_CLASS (meta_frames_parent_class);
92 object = gobject_class->constructor (gtype, n_properties, properties);
93
94 g_object_set (object,
95 "type", GTK_WINDOW_POPUP,
96 NULL);
97
98 return object;
99 }
100
101 static void
meta_frames_class_init(MetaFramesClass * class)102 meta_frames_class_init (MetaFramesClass *class)
103 {
104 GObjectClass *gobject_class;
105 GtkWidgetClass *widget_class;
106
107 gobject_class = G_OBJECT_CLASS (class);
108 widget_class = (GtkWidgetClass*) class;
109
110 gobject_class->constructor = meta_frames_constructor;
111 gobject_class->finalize = meta_frames_finalize;
112
113 widget_class->destroy = meta_frames_destroy;
114
115 widget_class->style_updated = meta_frames_style_updated;
116
117 widget_class->draw = meta_frames_draw;
118 }
119
120 static gint
unsigned_long_equal(gconstpointer v1,gconstpointer v2)121 unsigned_long_equal (gconstpointer v1,
122 gconstpointer v2)
123 {
124 return *((const gulong*) v1) == *((const gulong*) v2);
125 }
126
127 static guint
unsigned_long_hash(gconstpointer v)128 unsigned_long_hash (gconstpointer v)
129 {
130 gulong val = * (const gulong *) v;
131
132 /* I'm not sure this works so well. */
133 #if GLIB_SIZEOF_LONG > 4
134 return (guint) (val ^ (val >> 32));
135 #else
136 return val;
137 #endif
138 }
139
140 static void
prefs_changed_callback(MetaPreference pref,void * data)141 prefs_changed_callback (MetaPreference pref,
142 void *data)
143 {
144 switch (pref)
145 {
146 case META_PREF_TITLEBAR_FONT:
147 meta_frames_font_changed (META_FRAMES (data));
148 break;
149 case META_PREF_BUTTON_LAYOUT:
150 meta_frames_button_layout_changed (META_FRAMES (data));
151 break;
152 default:
153 break;
154 }
155 }
156
157 static void
invalidate_whole_window(MetaUIFrame * frame)158 invalidate_whole_window (MetaUIFrame *frame)
159 {
160 if (!frame->is_frozen)
161 {
162 meta_window_x11_freeze_commits (frame->meta_window);
163 frame->is_frozen = TRUE;
164 }
165 gdk_window_invalidate_rect (frame->window, NULL, FALSE);
166 }
167
168 static MetaStyleInfo *
meta_frames_get_theme_variant(MetaFrames * frames,const gchar * variant)169 meta_frames_get_theme_variant (MetaFrames *frames,
170 const gchar *variant)
171 {
172 MetaStyleInfo *style_info;
173
174 style_info = g_hash_table_lookup (frames->style_variants, variant);
175 if (style_info == NULL)
176 {
177 style_info = meta_theme_create_style_info (gtk_widget_get_screen (GTK_WIDGET (frames)), variant);
178 g_hash_table_insert (frames->style_variants, g_strdup (variant), style_info);
179 }
180
181 return style_info;
182 }
183
184 static void
update_style_contexts(MetaFrames * frames)185 update_style_contexts (MetaFrames *frames)
186 {
187 MetaStyleInfo *style_info;
188 GList *variants, *variant;
189 GdkScreen *screen;
190
191 screen = gtk_widget_get_screen (GTK_WIDGET (frames));
192
193 if (frames->normal_style)
194 meta_style_info_unref (frames->normal_style);
195 frames->normal_style = meta_theme_create_style_info (screen, NULL);
196
197 variants = g_hash_table_get_keys (frames->style_variants);
198 for (variant = variants; variant; variant = variant->next)
199 {
200 style_info = meta_theme_create_style_info (screen, (char *)variant->data);
201 g_hash_table_insert (frames->style_variants,
202 g_strdup (variant->data), style_info);
203 }
204 g_list_free (variants);
205 }
206
207 static void
meta_frames_init(MetaFrames * frames)208 meta_frames_init (MetaFrames *frames)
209 {
210 frames->text_heights = g_hash_table_new (NULL, NULL);
211
212 frames->frames = g_hash_table_new (unsigned_long_hash, unsigned_long_equal);
213
214 frames->style_variants = g_hash_table_new_full (g_str_hash, g_str_equal,
215 g_free, (GDestroyNotify)meta_style_info_unref);
216
217 update_style_contexts (frames);
218
219 meta_prefs_add_listener (prefs_changed_callback, frames);
220 }
221
222 static void
listify_func(gpointer key,gpointer value,gpointer data)223 listify_func (gpointer key, gpointer value, gpointer data)
224 {
225 GSList **listp;
226
227 listp = data;
228 *listp = g_slist_prepend (*listp, value);
229 }
230
231 static void
meta_frames_destroy(GtkWidget * object)232 meta_frames_destroy (GtkWidget *object)
233 {
234 GSList *winlist;
235 GSList *tmp;
236 MetaFrames *frames;
237
238 frames = META_FRAMES (object);
239
240 winlist = NULL;
241 g_hash_table_foreach (frames->frames, listify_func, &winlist);
242
243 /* Unmanage all frames */
244 for (tmp = winlist; tmp != NULL; tmp = tmp->next)
245 {
246 MetaUIFrame *frame = tmp->data;
247 meta_ui_frame_unmanage (frame);
248 }
249 g_slist_free (winlist);
250
251 if (frames->normal_style)
252 {
253 meta_style_info_unref (frames->normal_style);
254 frames->normal_style = NULL;
255 }
256
257 if (frames->style_variants)
258 {
259 g_hash_table_destroy (frames->style_variants);
260 frames->style_variants = NULL;
261 }
262
263 GTK_WIDGET_CLASS (meta_frames_parent_class)->destroy (object);
264 }
265
266 static void
meta_frames_finalize(GObject * object)267 meta_frames_finalize (GObject *object)
268 {
269 MetaFrames *frames;
270
271 frames = META_FRAMES (object);
272
273 meta_prefs_remove_listener (prefs_changed_callback, frames);
274
275 g_hash_table_destroy (frames->text_heights);
276
277 g_assert (g_hash_table_size (frames->frames) == 0);
278 g_hash_table_destroy (frames->frames);
279
280 G_OBJECT_CLASS (meta_frames_parent_class)->finalize (object);
281 }
282
283 static void
queue_recalc_func(gpointer key,gpointer value,gpointer user_data)284 queue_recalc_func (gpointer key,
285 gpointer value,
286 gpointer user_data)
287 {
288 MetaUIFrame *frame = value;
289 MetaFrames *frames = user_data;
290
291 invalidate_whole_window (frame);
292 meta_x11_wm_queue_frame_resize (frames->x11_display,
293 frame->xwindow);
294
295 g_clear_object (&frame->text_layout);
296 }
297
298 static void
meta_frames_font_changed(MetaFrames * frames)299 meta_frames_font_changed (MetaFrames *frames)
300 {
301 if (g_hash_table_size (frames->text_heights) > 0)
302 {
303 g_hash_table_destroy (frames->text_heights);
304 frames->text_heights = g_hash_table_new (NULL, NULL);
305 }
306
307 /* Queue a draw/resize on all frames */
308 g_hash_table_foreach (frames->frames,
309 queue_recalc_func, frames);
310
311 }
312
313 static void
queue_draw_func(gpointer key,gpointer value,gpointer data)314 queue_draw_func (gpointer key, gpointer value, gpointer data)
315 {
316 MetaUIFrame *frame = value;
317 invalidate_whole_window (frame);
318 }
319
320 static void
meta_frames_button_layout_changed(MetaFrames * frames)321 meta_frames_button_layout_changed (MetaFrames *frames)
322 {
323 g_hash_table_foreach (frames->frames,
324 queue_draw_func, frames);
325 }
326
327 static void
reattach_style_func(gpointer key,gpointer value,gpointer data)328 reattach_style_func (gpointer key, gpointer value, gpointer data)
329 {
330 MetaUIFrame *frame = value;
331 meta_ui_frame_attach_style (frame);
332 }
333
334 static void
meta_frames_style_updated(GtkWidget * widget)335 meta_frames_style_updated (GtkWidget *widget)
336 {
337 MetaFrames *frames;
338
339 frames = META_FRAMES (widget);
340
341 meta_frames_font_changed (frames);
342
343 update_style_contexts (frames);
344
345 g_hash_table_foreach (frames->frames, reattach_style_func, NULL);
346
347 meta_display_queue_retheme_all_windows (meta_get_display ());
348
349 GTK_WIDGET_CLASS (meta_frames_parent_class)->style_updated (widget);
350 }
351
352 static void
meta_ui_frame_ensure_layout(MetaUIFrame * frame,MetaFrameType type)353 meta_ui_frame_ensure_layout (MetaUIFrame *frame,
354 MetaFrameType type)
355 {
356 MetaFrames *frames = frame->frames;
357 GtkWidget *widget;
358 MetaFrameLayout *layout;
359
360 widget = GTK_WIDGET (frames);
361
362 g_return_if_fail (gtk_widget_get_realized (widget));
363
364 layout = meta_theme_get_frame_layout (meta_theme_get_default (), type);
365
366 if (layout != frame->cache_layout)
367 g_clear_object (&frame->text_layout);
368
369 frame->cache_layout = layout;
370
371 if (frame->text_layout == NULL)
372 {
373 gpointer key, value;
374 PangoFontDescription *font_desc;
375 int size;
376
377 frame->text_layout = gtk_widget_create_pango_layout (widget, frame->title);
378
379 pango_layout_set_ellipsize (frame->text_layout, PANGO_ELLIPSIZE_END);
380 pango_layout_set_auto_dir (frame->text_layout, FALSE);
381 pango_layout_set_single_paragraph_mode (frame->text_layout, TRUE);
382
383 font_desc = meta_style_info_create_font_desc (frame->style_info);
384 meta_frame_layout_apply_scale (layout, font_desc);
385
386 size = pango_font_description_get_size (font_desc);
387
388 if (g_hash_table_lookup_extended (frames->text_heights,
389 GINT_TO_POINTER (size),
390 &key, &value))
391 {
392 frame->text_height = GPOINTER_TO_INT (value);
393 }
394 else
395 {
396 frame->text_height =
397 meta_pango_font_desc_get_text_height (font_desc,
398 gtk_widget_get_pango_context (widget));
399
400 g_hash_table_replace (frames->text_heights,
401 GINT_TO_POINTER (size),
402 GINT_TO_POINTER (frame->text_height));
403 }
404
405 pango_layout_set_font_description (frame->text_layout,
406 font_desc);
407
408 pango_font_description_free (font_desc);
409 }
410 }
411
412 static void
meta_ui_frame_calc_geometry(MetaUIFrame * frame,MetaFrameGeometry * fgeom)413 meta_ui_frame_calc_geometry (MetaUIFrame *frame,
414 MetaFrameGeometry *fgeom)
415 {
416 MetaFrameFlags flags;
417 MetaFrameType type;
418 MetaButtonLayout button_layout;
419 MetaWindowX11 *window_x11 = META_WINDOW_X11 (frame->meta_window);
420 MetaRectangle client_rect;
421
422 flags = meta_frame_get_flags (frame->meta_window->frame);
423 type = meta_window_get_frame_type (frame->meta_window);
424
425 meta_ui_frame_ensure_layout (frame, type);
426
427 meta_prefs_get_button_layout (&button_layout);
428
429 client_rect = meta_window_x11_get_client_rect (window_x11);
430
431 meta_theme_calc_geometry (meta_theme_get_default (),
432 frame->style_info,
433 type,
434 frame->text_height,
435 flags,
436 client_rect.width,
437 client_rect.height,
438 &button_layout,
439 fgeom);
440 }
441
442 MetaFrames*
meta_frames_new(MetaX11Display * x11_display)443 meta_frames_new (MetaX11Display *x11_display)
444 {
445 MetaFrames *frames;
446
447 frames = g_object_new (META_TYPE_FRAMES,
448 "type", GTK_WINDOW_POPUP,
449 NULL);
450 frames->x11_display = x11_display;
451
452 /* Put the window at an arbitrary offscreen location; the one place
453 * it can't be is at -100x-100, since the meta_window_new() will
454 * mistake it for a window created via meta_create_offscreen_window()
455 * and ignore it, and we need this window to get frame-synchronization
456 * messages so that GTK+'s style change handling works.
457 */
458 gtk_window_move (GTK_WINDOW (frames), -200, -200);
459 gtk_window_resize (GTK_WINDOW (frames), 1, 1);
460
461 return frames;
462 }
463
464 static const char *
get_global_theme_variant(MetaFrames * frames)465 get_global_theme_variant (MetaFrames *frames)
466 {
467 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (frames));
468 GtkSettings *settings = gtk_settings_get_for_screen (screen);
469 gboolean dark_theme_requested;
470
471 g_object_get (settings,
472 "gtk-application-prefer-dark-theme", &dark_theme_requested,
473 NULL);
474
475 if (dark_theme_requested)
476 return "dark";
477
478 return NULL;
479 }
480
481 /* In order to use a style with a window it has to be attached to that
482 * window. Actually, the colormaps just have to match, but since GTK+
483 * already takes care of making sure that its cheap to attach a style
484 * to multiple windows with the same colormap, we can just go ahead
485 * and attach separately for each window.
486 */
487 static void
meta_ui_frame_attach_style(MetaUIFrame * frame)488 meta_ui_frame_attach_style (MetaUIFrame *frame)
489 {
490 MetaFrames *frames = frame->frames;
491 const char *variant;
492
493 if (frame->style_info != NULL)
494 meta_style_info_unref (frame->style_info);
495
496 variant = frame->meta_window->gtk_theme_variant;
497 if (variant == NULL)
498 variant = get_global_theme_variant (frame->frames);
499
500 if (variant == NULL || *variant == '\0')
501 frame->style_info = meta_style_info_ref (frames->normal_style);
502 else
503 frame->style_info = meta_style_info_ref (meta_frames_get_theme_variant (frames,
504 variant));
505 }
506
507 MetaUIFrame *
meta_frames_manage_window(MetaFrames * frames,MetaWindow * meta_window,Window xwindow,GdkWindow * window)508 meta_frames_manage_window (MetaFrames *frames,
509 MetaWindow *meta_window,
510 Window xwindow,
511 GdkWindow *window)
512 {
513 MetaUIFrame *frame;
514
515 g_assert (window);
516
517 frame = g_new (MetaUIFrame, 1);
518
519 frame->frames = frames;
520 frame->window = window;
521
522 gdk_window_set_user_data (frame->window, frames);
523
524 frame->style_info = NULL;
525
526 /* Don't set event mask here, it's in frame.c */
527
528 frame->xwindow = xwindow;
529 frame->meta_window = meta_window;
530 frame->cache_layout = NULL;
531 frame->text_layout = NULL;
532 frame->text_height = -1;
533 frame->title = NULL;
534 frame->prelit_control = META_FRAME_CONTROL_NONE;
535 frame->button_state = META_BUTTON_STATE_NORMAL;
536 frame->is_frozen = FALSE;
537
538 meta_x11_wm_grab_buttons (frames->x11_display, frame->xwindow);
539
540 g_hash_table_replace (frames->frames, &frame->xwindow, frame);
541
542 return frame;
543 }
544
545 void
meta_ui_frame_unmanage(MetaUIFrame * frame)546 meta_ui_frame_unmanage (MetaUIFrame *frame)
547 {
548 MetaFrames *frames = frame->frames;
549
550 /* restore the cursor */
551 meta_x11_wm_set_screen_cursor (frames->x11_display,
552 frame->xwindow,
553 META_CURSOR_DEFAULT);
554
555 gdk_window_set_user_data (frame->window, NULL);
556
557 g_hash_table_remove (frames->frames, &frame->xwindow);
558
559 meta_style_info_unref (frame->style_info);
560
561 gdk_window_destroy (frame->window);
562
563 if (frame->text_layout)
564 g_object_unref (G_OBJECT (frame->text_layout));
565
566 if (frame->is_frozen)
567 meta_window_x11_thaw_commits (frame->meta_window);
568
569 g_free (frame->title);
570
571 g_free (frame);
572 }
573
574 void
meta_ui_frame_get_borders(MetaUIFrame * frame,MetaFrameBorders * borders)575 meta_ui_frame_get_borders (MetaUIFrame *frame,
576 MetaFrameBorders *borders)
577 {
578 MetaFrameFlags flags;
579 MetaFrameType type;
580
581 flags = meta_frame_get_flags (frame->meta_window->frame);
582 type = meta_window_get_frame_type (frame->meta_window);
583
584 g_return_if_fail (type < META_FRAME_TYPE_LAST);
585
586 meta_ui_frame_ensure_layout (frame, type);
587
588 /* We can't get the full geometry, because that depends on
589 * the client window size and probably we're being called
590 * by the core move/resize code to decide on the client
591 * window size
592 */
593 meta_theme_get_frame_borders (meta_theme_get_default (),
594 frame->style_info,
595 type,
596 frame->text_height,
597 flags,
598 borders);
599 }
600
601 /* The visible frame rectangle surrounds the visible portion of the
602 * frame window; it subtracts only the invisible borders from the frame
603 * window's size.
604 */
605 static void
get_visible_frame_rect(MetaFrameGeometry * fgeom,cairo_rectangle_int_t * rect)606 get_visible_frame_rect (MetaFrameGeometry *fgeom,
607 cairo_rectangle_int_t *rect)
608 {
609 rect->x = fgeom->borders.invisible.left;
610 rect->y = fgeom->borders.invisible.top;
611 rect->width = fgeom->width - fgeom->borders.invisible.right - rect->x;
612 rect->height = fgeom->height - fgeom->borders.invisible.bottom - rect->y;
613 }
614
615 static cairo_region_t *
get_visible_region(MetaUIFrame * frame,MetaFrameGeometry * fgeom)616 get_visible_region (MetaUIFrame *frame,
617 MetaFrameGeometry *fgeom)
618 {
619 cairo_region_t *corners_region;
620 cairo_region_t *visible_region;
621 cairo_rectangle_int_t rect;
622 cairo_rectangle_int_t frame_rect;
623
624 corners_region = cairo_region_create ();
625 get_visible_frame_rect (fgeom, &frame_rect);
626
627 if (fgeom->top_left_corner_rounded_radius != 0)
628 {
629 const int corner = fgeom->top_left_corner_rounded_radius;
630 const float radius = corner;
631 int i;
632
633 for (i=0; i<corner; i++)
634 {
635 const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
636 rect.x = frame_rect.x;
637 rect.y = frame_rect.y + i;
638 rect.width = width;
639 rect.height = 1;
640
641 cairo_region_union_rectangle (corners_region, &rect);
642 }
643 }
644
645 if (fgeom->top_right_corner_rounded_radius != 0)
646 {
647 const int corner = fgeom->top_right_corner_rounded_radius;
648 const float radius = corner;
649 int i;
650
651 for (i=0; i<corner; i++)
652 {
653 const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
654 rect.x = frame_rect.x + frame_rect.width - width;
655 rect.y = frame_rect.y + i;
656 rect.width = width;
657 rect.height = 1;
658
659 cairo_region_union_rectangle (corners_region, &rect);
660 }
661 }
662
663 if (fgeom->bottom_left_corner_rounded_radius != 0)
664 {
665 const int corner = fgeom->bottom_left_corner_rounded_radius;
666 const float radius = corner;
667 int i;
668
669 for (i=0; i<corner; i++)
670 {
671 const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
672 rect.x = frame_rect.x;
673 rect.y = frame_rect.y + frame_rect.height - i - 1;
674 rect.width = width;
675 rect.height = 1;
676
677 cairo_region_union_rectangle (corners_region, &rect);
678 }
679 }
680
681 if (fgeom->bottom_right_corner_rounded_radius != 0)
682 {
683 const int corner = fgeom->bottom_right_corner_rounded_radius;
684 const float radius = corner;
685 int i;
686
687 for (i=0; i<corner; i++)
688 {
689 const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
690 rect.x = frame_rect.x + frame_rect.width - width;
691 rect.y = frame_rect.y + frame_rect.height - i - 1;
692 rect.width = width;
693 rect.height = 1;
694
695 cairo_region_union_rectangle (corners_region, &rect);
696 }
697 }
698
699 visible_region = cairo_region_create_rectangle (&frame_rect);
700 cairo_region_subtract (visible_region, corners_region);
701 cairo_region_destroy (corners_region);
702
703 return visible_region;
704 }
705
706 cairo_region_t *
meta_ui_frame_get_bounds(MetaUIFrame * frame)707 meta_ui_frame_get_bounds (MetaUIFrame *frame)
708 {
709 MetaFrameGeometry fgeom;
710 meta_ui_frame_calc_geometry (frame, &fgeom);
711 return get_visible_region (frame, &fgeom);
712 }
713
714 void
meta_ui_frame_move_resize(MetaUIFrame * frame,int x,int y,int width,int height)715 meta_ui_frame_move_resize (MetaUIFrame *frame,
716 int x, int y, int width, int height)
717 {
718 int old_width, old_height;
719
720 old_width = gdk_window_get_width (frame->window);
721 old_height = gdk_window_get_height (frame->window);
722
723 gdk_window_move_resize (frame->window, x, y, width, height);
724
725 if (old_width != width || old_height != height)
726 invalidate_whole_window (frame);
727 }
728
729 void
meta_ui_frame_queue_draw(MetaUIFrame * frame)730 meta_ui_frame_queue_draw (MetaUIFrame *frame)
731 {
732 invalidate_whole_window (frame);
733 }
734
735 void
meta_ui_frame_set_title(MetaUIFrame * frame,const char * title)736 meta_ui_frame_set_title (MetaUIFrame *frame,
737 const char *title)
738 {
739 g_free (frame->title);
740 frame->title = g_strdup (title);
741
742 g_clear_object (&frame->text_layout);
743
744 invalidate_whole_window (frame);
745 }
746
747 void
meta_ui_frame_update_style(MetaUIFrame * frame)748 meta_ui_frame_update_style (MetaUIFrame *frame)
749 {
750 meta_ui_frame_attach_style (frame);
751 invalidate_whole_window (frame);
752 }
753
754 static void
redraw_control(MetaUIFrame * frame,MetaFrameControl control)755 redraw_control (MetaUIFrame *frame,
756 MetaFrameControl control)
757 {
758 MetaFrameGeometry fgeom;
759 GdkRectangle *rect;
760
761 meta_ui_frame_calc_geometry (frame, &fgeom);
762
763 rect = control_rect (control, &fgeom);
764
765 gdk_window_invalidate_rect (frame->window, rect, FALSE);
766 }
767
768 static gboolean
meta_frame_titlebar_event(MetaUIFrame * frame,const ClutterEvent * event,int action)769 meta_frame_titlebar_event (MetaUIFrame *frame,
770 const ClutterEvent *event,
771 int action)
772 {
773 MetaFrameFlags flags;
774 MetaX11Display *x11_display;
775 uint32_t evtime;
776 float x, y;
777
778 g_assert (event->type == CLUTTER_BUTTON_PRESS ||
779 event->type == CLUTTER_TOUCH_BEGIN);
780
781 x11_display = frame->frames->x11_display;
782
783 flags = meta_frame_get_flags (frame->meta_window->frame);
784
785 evtime = clutter_event_get_time (event);
786 clutter_event_get_coords (event, &x, &y);
787
788 switch (action)
789 {
790 case G_DESKTOP_TITLEBAR_ACTION_TOGGLE_SHADE:
791 {
792 if (flags & META_FRAME_ALLOWS_SHADE)
793 {
794 if (flags & META_FRAME_SHADED)
795 meta_window_unshade (frame->meta_window, evtime);
796 else
797 meta_window_shade (frame->meta_window, evtime);
798 }
799 }
800 break;
801
802 case G_DESKTOP_TITLEBAR_ACTION_TOGGLE_MAXIMIZE:
803 {
804 if (flags & META_FRAME_ALLOWS_MAXIMIZE)
805 {
806 meta_x11_wm_toggle_maximize (x11_display, frame->xwindow);
807 }
808 }
809 break;
810
811 case G_DESKTOP_TITLEBAR_ACTION_TOGGLE_MAXIMIZE_HORIZONTALLY:
812 {
813 if (flags & META_FRAME_ALLOWS_MAXIMIZE)
814 {
815 meta_x11_wm_toggle_maximize_horizontally (x11_display,
816 frame->xwindow);
817 }
818 }
819 break;
820
821 case G_DESKTOP_TITLEBAR_ACTION_TOGGLE_MAXIMIZE_VERTICALLY:
822 {
823 if (flags & META_FRAME_ALLOWS_MAXIMIZE)
824 {
825 meta_x11_wm_toggle_maximize_vertically (x11_display, frame->xwindow);
826 }
827 }
828 break;
829
830 case G_DESKTOP_TITLEBAR_ACTION_MINIMIZE:
831 {
832 if (flags & META_FRAME_ALLOWS_MINIMIZE)
833 meta_window_minimize (frame->meta_window);
834 }
835 break;
836
837 case G_DESKTOP_TITLEBAR_ACTION_NONE:
838 /* Yaay, a sane user that doesn't use that other weird crap! */
839 break;
840
841 case G_DESKTOP_TITLEBAR_ACTION_LOWER:
842 meta_x11_wm_user_lower_and_unfocus (x11_display,
843 frame->xwindow,
844 evtime);
845 break;
846
847 case G_DESKTOP_TITLEBAR_ACTION_MENU:
848 meta_x11_wm_show_window_menu (x11_display,
849 frame->xwindow,
850 META_WINDOW_MENU_WM,
851 x, y, evtime);
852 break;
853 }
854
855 return TRUE;
856 }
857
858 static gboolean
meta_frame_double_click_event(MetaUIFrame * frame,const ClutterEvent * event)859 meta_frame_double_click_event (MetaUIFrame *frame,
860 const ClutterEvent *event)
861 {
862 int action = meta_prefs_get_action_double_click_titlebar ();
863
864 return meta_frame_titlebar_event (frame, event, action);
865 }
866
867 static gboolean
meta_frame_middle_click_event(MetaUIFrame * frame,ClutterButtonEvent * event)868 meta_frame_middle_click_event (MetaUIFrame *frame,
869 ClutterButtonEvent *event)
870 {
871 int action = meta_prefs_get_action_middle_click_titlebar();
872
873 return meta_frame_titlebar_event (frame, (const ClutterEvent *) event,
874 action);
875 }
876
877 static gboolean
meta_frame_right_click_event(MetaUIFrame * frame,ClutterButtonEvent * event)878 meta_frame_right_click_event (MetaUIFrame *frame,
879 ClutterButtonEvent *event)
880 {
881 int action = meta_prefs_get_action_right_click_titlebar();
882
883 return meta_frame_titlebar_event (frame, (const ClutterEvent *) event,
884 action);
885 }
886
887 static gboolean
meta_frames_try_grab_op(MetaUIFrame * frame,MetaGrabOp op,gdouble grab_x,gdouble grab_y,guint32 time)888 meta_frames_try_grab_op (MetaUIFrame *frame,
889 MetaGrabOp op,
890 gdouble grab_x,
891 gdouble grab_y,
892 guint32 time)
893 {
894 MetaFrames *frames = frame->frames;
895 gboolean ret;
896
897 ret = meta_x11_wm_begin_grab_op (frames->x11_display,
898 frame->xwindow,
899 op,
900 FALSE,
901 TRUE,
902 frame->grab_button,
903 0,
904 time,
905 grab_x, grab_y);
906 if (!ret)
907 {
908 frames->current_grab_op = op;
909 frames->grab_frame = frame;
910 frames->grab_x = grab_x;
911 frames->grab_y = grab_y;
912 }
913 else
914 frames->grab_touch = NULL;
915
916 return ret;
917 }
918
919 static gboolean
meta_frames_retry_grab_op(MetaFrames * frames,guint time)920 meta_frames_retry_grab_op (MetaFrames *frames,
921 guint time)
922 {
923 MetaGrabOp op;
924 gboolean ret;
925
926 if (frames->current_grab_op == META_GRAB_OP_NONE)
927 return TRUE;
928
929 op = frames->current_grab_op;
930 frames->current_grab_op = META_GRAB_OP_NONE;
931
932 ret = meta_x11_wm_begin_grab_op (frames->x11_display,
933 frames->grab_frame->xwindow,
934 op,
935 FALSE,
936 TRUE,
937 frames->grab_frame->grab_button,
938 0,
939 time,
940 frames->grab_x,
941 frames->grab_y);
942 if (ret)
943 frames->grab_touch = NULL;
944
945 return ret;
946 }
947
948 static MetaGrabOp
grab_op_from_resize_control(MetaFrameControl control)949 grab_op_from_resize_control (MetaFrameControl control)
950 {
951 switch (control)
952 {
953 case META_FRAME_CONTROL_RESIZE_SE:
954 return META_GRAB_OP_RESIZING_SE;
955 case META_FRAME_CONTROL_RESIZE_S:
956 return META_GRAB_OP_RESIZING_S;
957 case META_FRAME_CONTROL_RESIZE_SW:
958 return META_GRAB_OP_RESIZING_SW;
959 case META_FRAME_CONTROL_RESIZE_NE:
960 return META_GRAB_OP_RESIZING_NE;
961 case META_FRAME_CONTROL_RESIZE_N:
962 return META_GRAB_OP_RESIZING_N;
963 case META_FRAME_CONTROL_RESIZE_NW:
964 return META_GRAB_OP_RESIZING_NW;
965 case META_FRAME_CONTROL_RESIZE_E:
966 return META_GRAB_OP_RESIZING_E;
967 case META_FRAME_CONTROL_RESIZE_W:
968 return META_GRAB_OP_RESIZING_W;
969 default:
970 g_assert_not_reached ();
971 return META_GRAB_OP_NONE;
972 }
973 }
974
975 static guint
get_action(const ClutterEvent * event)976 get_action (const ClutterEvent *event)
977 {
978 if (event->type == CLUTTER_BUTTON_PRESS ||
979 event->type == CLUTTER_BUTTON_RELEASE)
980 {
981 switch (event->button.button)
982 {
983 case CLUTTER_BUTTON_PRIMARY:
984 if (clutter_event_get_click_count (event) == 2)
985 return META_ACTION_DOUBLE_CLICK;
986 else
987 return META_ACTION_CLICK;
988 case CLUTTER_BUTTON_SECONDARY:
989 return META_ACTION_RIGHT_CLICK;
990 case CLUTTER_BUTTON_MIDDLE:
991 return META_ACTION_MIDDLE_CLICK;
992 default:
993 meta_verbose ("No action triggered for button %u %s",
994 event->button.button,
995 (event->type == CLUTTER_BUTTON_PRESS) ? "press" : "release");
996 }
997 }
998 else if (event->type == CLUTTER_TOUCH_BEGIN ||
999 event->type == CLUTTER_TOUCH_UPDATE ||
1000 event->type == CLUTTER_TOUCH_END)
1001 {
1002 return META_ACTION_CLICK;
1003 }
1004
1005 return META_ACTION_IGNORE;
1006 }
1007
1008 static uint32_t
get_button_number(const ClutterEvent * event)1009 get_button_number (const ClutterEvent *event)
1010 {
1011 if (event->type == CLUTTER_TOUCH_BEGIN ||
1012 event->type == CLUTTER_TOUCH_UPDATE ||
1013 event->type == CLUTTER_TOUCH_END)
1014 return -1;
1015 else if (event->type == CLUTTER_BUTTON_PRESS ||
1016 event->type == CLUTTER_BUTTON_RELEASE)
1017 return clutter_event_get_button (event);
1018
1019 g_assert_not_reached ();
1020 return -1;
1021 }
1022
1023 static gboolean
meta_frame_left_click_event(MetaUIFrame * frame,const ClutterEvent * event)1024 meta_frame_left_click_event (MetaUIFrame *frame,
1025 const ClutterEvent *event)
1026 {
1027 MetaX11Display *x11_display = frame->frames->x11_display;
1028 MetaFrameControl control;
1029 guint32 evtime;
1030 gfloat x, y;
1031
1032 evtime = clutter_event_get_time (event);
1033 clutter_event_get_coords (event, &x, &y);
1034 control = get_control (frame, x, y);
1035
1036 switch (control)
1037 {
1038 case META_FRAME_CONTROL_MAXIMIZE:
1039 case META_FRAME_CONTROL_UNMAXIMIZE:
1040 case META_FRAME_CONTROL_MINIMIZE:
1041 case META_FRAME_CONTROL_DELETE:
1042 case META_FRAME_CONTROL_MENU:
1043 frame->grab_button = get_button_number (event);
1044 frame->button_state = META_BUTTON_STATE_PRESSED;
1045 frame->prelit_control = control;
1046 redraw_control (frame, control);
1047
1048 if (control == META_FRAME_CONTROL_MENU)
1049 {
1050 MetaFrameGeometry fgeom;
1051 GdkRectangle *rect;
1052 MetaRectangle root_rect;
1053 int win_x, win_y;
1054
1055 meta_ui_frame_calc_geometry (frame, &fgeom);
1056
1057 rect = control_rect (control, &fgeom);
1058
1059 gdk_window_get_position (frame->window, &win_x, &win_y);
1060
1061 root_rect.x = win_x + rect->x;
1062 root_rect.y = win_y + rect->y;
1063 root_rect.width = rect->width;
1064 root_rect.height = rect->height;
1065
1066 /* if the compositor takes a grab for showing the menu, we will
1067 * get a LeaveNotify event we want to ignore, to keep the pressed
1068 * button state while the menu is open
1069 */
1070 frame->maybe_ignore_leave_notify = TRUE;
1071 meta_x11_wm_show_window_menu_for_rect (x11_display,
1072 frame->xwindow,
1073 META_WINDOW_MENU_WM,
1074 &root_rect,
1075 evtime);
1076 }
1077 else
1078 {
1079 meta_frames_try_grab_op (frame, META_GRAB_OP_FRAME_BUTTON,
1080 x, y, evtime);
1081 }
1082
1083 return TRUE;
1084 case META_FRAME_CONTROL_RESIZE_SE:
1085 case META_FRAME_CONTROL_RESIZE_S:
1086 case META_FRAME_CONTROL_RESIZE_SW:
1087 case META_FRAME_CONTROL_RESIZE_NE:
1088 case META_FRAME_CONTROL_RESIZE_N:
1089 case META_FRAME_CONTROL_RESIZE_NW:
1090 case META_FRAME_CONTROL_RESIZE_E:
1091 case META_FRAME_CONTROL_RESIZE_W:
1092 meta_frames_try_grab_op (frame,
1093 grab_op_from_resize_control (control),
1094 x, y, evtime);
1095
1096 return TRUE;
1097 case META_FRAME_CONTROL_TITLE:
1098 {
1099 MetaFrameFlags flags = meta_frame_get_flags (frame->meta_window->frame);
1100
1101 if (flags & META_FRAME_ALLOWS_MOVE)
1102 {
1103 meta_frames_try_grab_op (frame,
1104 META_GRAB_OP_MOVING,
1105 x, y, evtime);
1106 }
1107 }
1108
1109 return TRUE;
1110 case META_FRAME_CONTROL_NONE:
1111 /* We can get this for example when trying to resize window
1112 * that cannot be resized (e. g. it is maximized and the theme
1113 * currently used has borders for maximized windows), see #751884 */
1114 return FALSE;
1115 case META_FRAME_CONTROL_CLIENT_AREA:
1116 /* This can happen with broken gtk themes that have a larger shadow size
1117 * in the unfocused state than in the focused one. Then when clicking
1118 * below the titlebar area in the unfocused state would still be
1119 * considered a click on the titlebar due to it being shifted down because
1120 * of the shadow. This then causes the window to be focused before this
1121 * function is called, which removes the shadow such that the same
1122 * position is now considered to be on the client area */
1123 return FALSE;
1124 default:
1125 g_assert_not_reached ();
1126 return FALSE;
1127 }
1128 }
1129
1130 static gboolean
handle_press_event(MetaUIFrame * frame,const ClutterEvent * event)1131 handle_press_event (MetaUIFrame *frame,
1132 const ClutterEvent *event)
1133 {
1134 MetaFrameControl control;
1135 uint32_t evtime, action;
1136 float x, y;
1137
1138 g_assert (event->type == CLUTTER_BUTTON_PRESS ||
1139 event->type == CLUTTER_TOUCH_BEGIN);
1140
1141 action = get_action (event);
1142 if (action == META_ACTION_IGNORE)
1143 return FALSE;
1144
1145 evtime = clutter_event_get_time (event);
1146 clutter_event_get_coords (event, &x, &y);
1147 control = get_control (frame, x, y);
1148 /* don't do the rest of this if on client area */
1149 if (control == META_FRAME_CONTROL_CLIENT_AREA)
1150 return FALSE; /* not on the frame, just passed through from client */
1151
1152 if (action == META_ACTION_CLICK &&
1153 !(control == META_FRAME_CONTROL_MINIMIZE ||
1154 control == META_FRAME_CONTROL_DELETE ||
1155 control == META_FRAME_CONTROL_MAXIMIZE))
1156 {
1157 meta_topic (META_DEBUG_FOCUS,
1158 "Focusing window with frame 0x%lx due to button 1 press",
1159 frame->xwindow);
1160 meta_window_focus (frame->meta_window, evtime);
1161 }
1162
1163 /* We want to shade even if we have a GrabOp, since we'll have a move grab
1164 * if we double click the titlebar.
1165 */
1166 if (control == META_FRAME_CONTROL_TITLE &&
1167 action == META_ACTION_DOUBLE_CLICK)
1168 {
1169 meta_x11_wm_end_grab_op (frame->frames->x11_display, evtime);
1170 return meta_frame_double_click_event (frame, event);
1171 }
1172
1173 if (meta_x11_wm_get_grab_op (frame->frames->x11_display) != META_GRAB_OP_NONE)
1174 return FALSE; /* already up to something */
1175
1176 frame->grab_button = get_button_number (event);
1177
1178 switch (action)
1179 {
1180 case META_ACTION_CLICK:
1181 return meta_frame_left_click_event (frame, event);
1182 case META_ACTION_MIDDLE_CLICK:
1183 return meta_frame_middle_click_event (frame, (ClutterButtonEvent *) event);
1184 case META_ACTION_RIGHT_CLICK:
1185 return meta_frame_right_click_event (frame, (ClutterButtonEvent *) event);
1186 default:
1187 return FALSE;
1188 }
1189 }
1190
1191 static gboolean
handle_release_event(MetaUIFrame * frame,const ClutterEvent * event)1192 handle_release_event (MetaUIFrame *frame,
1193 const ClutterEvent *event)
1194 {
1195 guint32 evtime, button;
1196 gfloat x, y;
1197
1198 g_assert (event->type == CLUTTER_BUTTON_RELEASE ||
1199 event->type == CLUTTER_TOUCH_END);
1200
1201 evtime = clutter_event_get_time (event);
1202 clutter_event_get_coords (event, &x, &y);
1203 button = get_button_number (event);
1204
1205 frame->frames->current_grab_op = META_GRAB_OP_NONE;
1206 meta_x11_wm_end_grab_op (frame->frames->x11_display, evtime);
1207
1208 /* We only handle the releases we handled the presses for (things
1209 * involving frame controls). Window ops that don't require a
1210 * frame are handled in the Xlib part of the code, display.c/window.c
1211 */
1212 if (((int) button) == frame->grab_button &&
1213 frame->button_state == META_BUTTON_STATE_PRESSED)
1214 {
1215 switch (frame->prelit_control)
1216 {
1217 case META_FRAME_CONTROL_MINIMIZE:
1218 meta_window_minimize (frame->meta_window);
1219 break;
1220 case META_FRAME_CONTROL_MAXIMIZE:
1221 /* Focus the window on the maximize */
1222 meta_window_focus (frame->meta_window, evtime);
1223 if (meta_prefs_get_raise_on_click ())
1224 meta_window_raise (frame->meta_window);
1225 meta_window_maximize (frame->meta_window, META_MAXIMIZE_BOTH);
1226 break;
1227 case META_FRAME_CONTROL_UNMAXIMIZE:
1228 if (meta_prefs_get_raise_on_click ())
1229 meta_window_raise (frame->meta_window);
1230 meta_window_unmaximize (frame->meta_window, META_MAXIMIZE_BOTH);
1231 break;
1232 case META_FRAME_CONTROL_DELETE:
1233 meta_window_delete (frame->meta_window, evtime);
1234 break;
1235 default:
1236 break;
1237 }
1238
1239 /* Update the prelit control regardless of what button the mouse
1240 * was released over; needed so that the new button can become
1241 * prelit so to let the user know that it can now be pressed.
1242 * :)
1243 */
1244 MetaFrameControl control = get_control (frame, x, y);
1245 meta_ui_frame_update_prelit_control (frame, control);
1246 }
1247
1248 return TRUE;
1249 }
1250
1251 static void
meta_ui_frame_update_prelit_control(MetaUIFrame * frame,MetaFrameControl control)1252 meta_ui_frame_update_prelit_control (MetaUIFrame *frame,
1253 MetaFrameControl control)
1254 {
1255 MetaFrameControl old_control;
1256 MetaCursor cursor;
1257
1258 meta_verbose ("Updating prelit control from %u to %u",
1259 frame->prelit_control, control);
1260
1261 cursor = META_CURSOR_DEFAULT;
1262
1263 switch (control)
1264 {
1265 case META_FRAME_CONTROL_CLIENT_AREA:
1266 break;
1267 case META_FRAME_CONTROL_NONE:
1268 break;
1269 case META_FRAME_CONTROL_TITLE:
1270 break;
1271 case META_FRAME_CONTROL_DELETE:
1272 break;
1273 case META_FRAME_CONTROL_MENU:
1274 break;
1275 case META_FRAME_CONTROL_MINIMIZE:
1276 break;
1277 case META_FRAME_CONTROL_MAXIMIZE:
1278 break;
1279 case META_FRAME_CONTROL_UNMAXIMIZE:
1280 break;
1281 case META_FRAME_CONTROL_RESIZE_SE:
1282 cursor = META_CURSOR_SE_RESIZE;
1283 break;
1284 case META_FRAME_CONTROL_RESIZE_S:
1285 cursor = META_CURSOR_SOUTH_RESIZE;
1286 break;
1287 case META_FRAME_CONTROL_RESIZE_SW:
1288 cursor = META_CURSOR_SW_RESIZE;
1289 break;
1290 case META_FRAME_CONTROL_RESIZE_N:
1291 cursor = META_CURSOR_NORTH_RESIZE;
1292 break;
1293 case META_FRAME_CONTROL_RESIZE_NE:
1294 cursor = META_CURSOR_NE_RESIZE;
1295 break;
1296 case META_FRAME_CONTROL_RESIZE_NW:
1297 cursor = META_CURSOR_NW_RESIZE;
1298 break;
1299 case META_FRAME_CONTROL_RESIZE_W:
1300 cursor = META_CURSOR_WEST_RESIZE;
1301 break;
1302 case META_FRAME_CONTROL_RESIZE_E:
1303 cursor = META_CURSOR_EAST_RESIZE;
1304 break;
1305 }
1306
1307 /* set/unset the prelight cursor */
1308 meta_x11_wm_set_screen_cursor (frame->frames->x11_display,
1309 frame->xwindow,
1310 cursor);
1311
1312 switch (control)
1313 {
1314 case META_FRAME_CONTROL_MENU:
1315 case META_FRAME_CONTROL_MINIMIZE:
1316 case META_FRAME_CONTROL_MAXIMIZE:
1317 case META_FRAME_CONTROL_DELETE:
1318 case META_FRAME_CONTROL_UNMAXIMIZE:
1319 /* leave control set */
1320 break;
1321 default:
1322 /* Only prelight buttons */
1323 control = META_FRAME_CONTROL_NONE;
1324 break;
1325 }
1326
1327 if (control == frame->prelit_control &&
1328 frame->button_state == META_BUTTON_STATE_PRELIGHT)
1329 return;
1330
1331 /* Save the old control so we can unprelight it */
1332 old_control = frame->prelit_control;
1333
1334 frame->button_state = META_BUTTON_STATE_PRELIGHT;
1335 frame->prelit_control = control;
1336
1337 redraw_control (frame, old_control);
1338 redraw_control (frame, control);
1339 }
1340
1341 static gboolean
handle_motion_event(MetaUIFrame * frame,const ClutterEvent * event)1342 handle_motion_event (MetaUIFrame *frame,
1343 const ClutterEvent *event)
1344 {
1345 MetaFrames *frames = frame->frames;
1346 MetaFrameControl control;
1347 ClutterModifierType modifiers;
1348 guint32 evtime;
1349 gfloat x, y;
1350
1351 g_assert (event->type == CLUTTER_MOTION ||
1352 event->type == CLUTTER_TOUCH_UPDATE);
1353
1354 modifiers = clutter_event_get_state (event);
1355 evtime = clutter_event_get_time (event);
1356 clutter_event_get_coords (event, &x, &y);
1357 control = get_control (frame, x, y);
1358
1359 if (frame->button_state == META_BUTTON_STATE_PRESSED)
1360 {
1361 /* If the user leaves the frame button, set the state
1362 * back to normal and redraw. */
1363 if (frame->prelit_control != control)
1364 {
1365 frame->button_state = META_BUTTON_STATE_NORMAL;
1366 redraw_control (frame, frame->prelit_control);
1367 }
1368 }
1369 else
1370 {
1371 /* Update prelit control and cursor */
1372 meta_ui_frame_update_prelit_control (frame, control);
1373 }
1374
1375 if (frames->current_grab_op != META_GRAB_OP_NONE &&
1376 (event->type == CLUTTER_TOUCH_UPDATE ||
1377 (event->type == CLUTTER_MOTION &&
1378 (modifiers & CLUTTER_BUTTON1_MASK))))
1379 meta_frames_retry_grab_op (frames, evtime);
1380
1381 return TRUE;
1382 }
1383
1384 static cairo_region_t *
get_visible_frame_border_region(MetaUIFrame * frame)1385 get_visible_frame_border_region (MetaUIFrame *frame)
1386 {
1387 cairo_rectangle_int_t area;
1388 cairo_region_t *frame_border;
1389 MetaFrameFlags flags;
1390 MetaFrameType type;
1391 MetaFrameBorders borders;
1392 MetaRectangle buffer_rect = frame->meta_window->buffer_rect;
1393
1394 flags = meta_frame_get_flags (frame->meta_window->frame);
1395 type = meta_window_get_frame_type (frame->meta_window);
1396
1397 meta_theme_get_frame_borders (meta_theme_get_default (), frame->style_info,
1398 type, frame->text_height, flags,
1399 &borders);
1400
1401 /* Frame rect */
1402 area.x = 0;
1403 area.y = 0;
1404 area.width = buffer_rect.width;
1405 area.height = buffer_rect.height;
1406
1407 frame_border = cairo_region_create_rectangle (&area);
1408
1409 /* Client rect */
1410 area.x += borders.total.left;
1411 area.y += borders.total.top;
1412 area.width -= borders.total.left + borders.total.right;
1413 area.height -= borders.total.top + borders.total.bottom;
1414
1415 /* Visible frame border */
1416 cairo_region_subtract_rectangle (frame_border, &area);
1417 return frame_border;
1418 }
1419
1420 /*
1421 * Draw the opaque and semi-opaque pixels of this frame into a mask.
1422 *
1423 * (0,0) in Cairo coordinates is assumed to be the top left corner of the
1424 * invisible border.
1425 *
1426 * The parts of @cr's surface in the clip region are assumed to be
1427 * initialized to fully-transparent, and the clip region is assumed to
1428 * contain the invisible border and the visible parts of the frame, but
1429 * not the client area.
1430 *
1431 * This function uses @cr to draw pixels of arbitrary color (it will
1432 * typically be drawing in a %CAIRO_FORMAT_A8 surface, so the color is
1433 * discarded anyway) with appropriate alpha values to reproduce this
1434 * frame's alpha channel, as a mask to be applied to an opaque pixmap.
1435 *
1436 * @frame: This frame
1437 * @frame_rect: The frame rect
1438 * @cr: Used to draw the resulting mask
1439 */
1440 void
meta_ui_frame_get_mask(MetaUIFrame * frame,cairo_rectangle_int_t * frame_rect,cairo_t * cr)1441 meta_ui_frame_get_mask (MetaUIFrame *frame,
1442 cairo_rectangle_int_t *frame_rect,
1443 cairo_t *cr)
1444 {
1445 MetaFrameBorders borders;
1446 MetaFrameFlags flags;
1447 cairo_surface_t *surface;
1448 double xscale, yscale;
1449 int scale;
1450
1451 flags = meta_frame_get_flags (frame->meta_window->frame);
1452
1453 meta_style_info_set_flags (frame->style_info, flags);
1454 meta_ui_frame_get_borders (frame, &borders);
1455
1456 /* See comment in meta_frame_layout_draw_with_style() for details on HiDPI handling */
1457 scale = meta_theme_get_window_scaling_factor ();
1458 surface = cairo_get_target (cr);
1459 cairo_surface_get_device_scale (surface, &xscale, &yscale);
1460 cairo_surface_set_device_scale (surface, scale, scale);
1461
1462 gtk_render_background (frame->style_info->styles[META_STYLE_ELEMENT_FRAME], cr,
1463 borders.invisible.left / scale,
1464 borders.invisible.top / scale,
1465 frame_rect->width / scale, frame_rect->height / scale);
1466 gtk_render_background (frame->style_info->styles[META_STYLE_ELEMENT_TITLEBAR], cr,
1467 borders.invisible.left / scale,
1468 borders.invisible.top / scale,
1469 frame_rect->width / scale, borders.total.top / scale);
1470
1471 cairo_surface_set_device_scale (surface, xscale, yscale);
1472 }
1473
1474 /* XXX -- this is disgusting. Find a better approach here.
1475 * Use multiple widgets? */
1476 static MetaUIFrame *
find_frame_to_draw(MetaFrames * frames,cairo_t * cr)1477 find_frame_to_draw (MetaFrames *frames,
1478 cairo_t *cr)
1479 {
1480 GHashTableIter iter;
1481 MetaUIFrame *frame;
1482
1483 g_hash_table_iter_init (&iter, frames->frames);
1484 while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &frame))
1485 if (gtk_cairo_should_draw_window (cr, frame->window))
1486 return frame;
1487
1488 return NULL;
1489 }
1490
1491 static gboolean
meta_frames_draw(GtkWidget * widget,cairo_t * cr)1492 meta_frames_draw (GtkWidget *widget,
1493 cairo_t *cr)
1494 {
1495 MetaUIFrame *frame;
1496 MetaFrames *frames;
1497 cairo_region_t *region;
1498
1499 frames = META_FRAMES (widget);
1500
1501 frame = find_frame_to_draw (frames, cr);
1502 if (frame == NULL)
1503 return FALSE;
1504
1505 region = get_visible_frame_border_region (frame);
1506 gdk_cairo_region (cr, region);
1507 cairo_clip (cr);
1508
1509 /* The target may be cleared to black or transparent, depending
1510 * on the frame's visual; we don't want decorations to appear
1511 * differently when the theme's decorations aren't fully opaque,
1512 * so clear to black first
1513 */
1514 cairo_paint (cr);
1515
1516 meta_ui_frame_paint (frame, cr);
1517 cairo_region_destroy (region);
1518
1519 return TRUE;
1520 }
1521
1522 static void
meta_ui_frame_paint(MetaUIFrame * frame,cairo_t * cr)1523 meta_ui_frame_paint (MetaUIFrame *frame,
1524 cairo_t *cr)
1525 {
1526 MetaFrameFlags flags;
1527 MetaFrameType type;
1528 cairo_surface_t *mini_icon;
1529 MetaButtonState button_states[META_BUTTON_TYPE_LAST];
1530 int i;
1531 int button_type = -1;
1532 MetaButtonLayout button_layout;
1533 MetaWindowX11 *window_x11 = META_WINDOW_X11 (frame->meta_window);
1534 MetaRectangle client_rect;
1535
1536 for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
1537 button_states[i] = META_BUTTON_STATE_NORMAL;
1538
1539 /* Set prelight state */
1540 switch (frame->prelit_control)
1541 {
1542 case META_FRAME_CONTROL_MENU:
1543 button_type = META_BUTTON_TYPE_MENU;
1544 break;
1545 case META_FRAME_CONTROL_MINIMIZE:
1546 button_type = META_BUTTON_TYPE_MINIMIZE;
1547 break;
1548 case META_FRAME_CONTROL_MAXIMIZE:
1549 button_type = META_BUTTON_TYPE_MAXIMIZE;
1550 break;
1551 case META_FRAME_CONTROL_UNMAXIMIZE:
1552 button_type = META_BUTTON_TYPE_MAXIMIZE;
1553 break;
1554 case META_FRAME_CONTROL_DELETE:
1555 button_type = META_BUTTON_TYPE_CLOSE;
1556 break;
1557 default:
1558 break;
1559 }
1560
1561 if (button_type > -1)
1562 button_states[button_type] = frame->button_state;
1563
1564 mini_icon = frame->meta_window->mini_icon;
1565 flags = meta_frame_get_flags (frame->meta_window->frame);
1566 type = meta_window_get_frame_type (frame->meta_window);
1567
1568 meta_ui_frame_ensure_layout (frame, type);
1569
1570 meta_prefs_get_button_layout (&button_layout);
1571
1572 client_rect = meta_window_x11_get_client_rect (window_x11);
1573
1574 meta_theme_draw_frame (meta_theme_get_default (),
1575 frame->style_info,
1576 cr,
1577 type,
1578 flags,
1579 client_rect.width,
1580 client_rect.height,
1581 frame->text_layout,
1582 frame->text_height,
1583 &button_layout,
1584 button_states,
1585 mini_icon);
1586
1587 if (frame->is_frozen)
1588 {
1589 meta_window_x11_thaw_commits (frame->meta_window);
1590 frame->is_frozen = FALSE;
1591 }
1592 }
1593
1594 static gboolean
handle_enter_notify_event(MetaUIFrame * frame,ClutterCrossingEvent * event)1595 handle_enter_notify_event (MetaUIFrame *frame,
1596 ClutterCrossingEvent *event)
1597 {
1598 MetaFrameControl control;
1599
1600 frame->maybe_ignore_leave_notify = FALSE;
1601
1602 control = get_control (frame, event->x, event->y);
1603 meta_ui_frame_update_prelit_control (frame, control);
1604
1605 return TRUE;
1606 }
1607
1608 static gboolean
handle_leave_notify_event(MetaUIFrame * frame,ClutterCrossingEvent * event)1609 handle_leave_notify_event (MetaUIFrame *frame,
1610 ClutterCrossingEvent *event)
1611 {
1612 MetaGrabOp grab_op;
1613
1614 grab_op = meta_x11_wm_get_grab_op (frame->frames->x11_display);
1615
1616 /* ignore the first LeaveNotify event after opening a window menu
1617 * if it is the result of a compositor grab
1618 */
1619 frame->maybe_ignore_leave_notify = frame->maybe_ignore_leave_notify &&
1620 grab_op == META_GRAB_OP_COMPOSITOR;
1621
1622 if (frame->maybe_ignore_leave_notify)
1623 return FALSE;
1624
1625 meta_ui_frame_update_prelit_control (frame, META_FRAME_CONTROL_NONE);
1626
1627 return TRUE;
1628 }
1629
1630 gboolean
meta_ui_frame_handle_event(MetaUIFrame * frame,const ClutterEvent * event)1631 meta_ui_frame_handle_event (MetaUIFrame *frame,
1632 const ClutterEvent *event)
1633 {
1634 if (event->type == CLUTTER_TOUCH_BEGIN ||
1635 event->type == CLUTTER_TOUCH_UPDATE ||
1636 event->type == CLUTTER_TOUCH_END)
1637 {
1638 ClutterEventSequence *sequence;
1639 MetaFrames *frames = frame->frames;
1640
1641 /* In X11, mutter sets up passive touch grabs which basically
1642 * means we handle those events twice (once through the passive
1643 * grab, and then through XISelectEvents).
1644 *
1645 * Receiving touch events here means we are going through the
1646 * former, but passive grabs are exclusively for gesture
1647 * recognition purposes.
1648 *
1649 * We do actually want this to happen though the regular event
1650 * selection paths to avoid breaking internal state, which means
1651 * we will get pointer events, because we don't select for XI_Touch*.
1652 */
1653 if (!meta_is_wayland_compositor ())
1654 return FALSE;
1655
1656 sequence = clutter_event_get_event_sequence (event);
1657
1658 /* Lock onto a single touch */
1659 if (frames->grab_touch && frames->grab_touch != sequence)
1660 return FALSE;
1661
1662 if (event->type == CLUTTER_TOUCH_BEGIN)
1663 frames->grab_touch = sequence;
1664 else if (event->type == CLUTTER_TOUCH_END)
1665 frames->grab_touch = NULL;
1666 }
1667
1668 switch (event->any.type)
1669 {
1670 case CLUTTER_BUTTON_PRESS:
1671 case CLUTTER_TOUCH_BEGIN:
1672 return handle_press_event (frame, event);
1673 case CLUTTER_BUTTON_RELEASE:
1674 case CLUTTER_TOUCH_END:
1675 return handle_release_event (frame, event);
1676 case CLUTTER_MOTION:
1677 case CLUTTER_TOUCH_UPDATE:
1678 return handle_motion_event (frame, event);
1679 case CLUTTER_ENTER:
1680 return handle_enter_notify_event (frame, (ClutterCrossingEvent *) event);
1681 case CLUTTER_LEAVE:
1682 return handle_leave_notify_event (frame, (ClutterCrossingEvent *) event);
1683 default:
1684 return FALSE;
1685 }
1686 }
1687
1688 static GdkRectangle*
control_rect(MetaFrameControl control,MetaFrameGeometry * fgeom)1689 control_rect (MetaFrameControl control,
1690 MetaFrameGeometry *fgeom)
1691 {
1692 GdkRectangle *rect;
1693
1694 rect = NULL;
1695 switch (control)
1696 {
1697 case META_FRAME_CONTROL_TITLE:
1698 rect = &fgeom->title_rect;
1699 break;
1700 case META_FRAME_CONTROL_DELETE:
1701 rect = &fgeom->close_rect.visible;
1702 break;
1703 case META_FRAME_CONTROL_MENU:
1704 rect = &fgeom->menu_rect.visible;
1705 break;
1706 case META_FRAME_CONTROL_MINIMIZE:
1707 rect = &fgeom->min_rect.visible;
1708 break;
1709 case META_FRAME_CONTROL_MAXIMIZE:
1710 case META_FRAME_CONTROL_UNMAXIMIZE:
1711 rect = &fgeom->max_rect.visible;
1712 break;
1713 case META_FRAME_CONTROL_RESIZE_SE:
1714 break;
1715 case META_FRAME_CONTROL_RESIZE_S:
1716 break;
1717 case META_FRAME_CONTROL_RESIZE_SW:
1718 break;
1719 case META_FRAME_CONTROL_RESIZE_N:
1720 break;
1721 case META_FRAME_CONTROL_RESIZE_NE:
1722 break;
1723 case META_FRAME_CONTROL_RESIZE_NW:
1724 break;
1725 case META_FRAME_CONTROL_RESIZE_W:
1726 break;
1727 case META_FRAME_CONTROL_RESIZE_E:
1728 break;
1729 case META_FRAME_CONTROL_NONE:
1730 break;
1731 case META_FRAME_CONTROL_CLIENT_AREA:
1732 break;
1733 }
1734
1735 return rect;
1736 }
1737
1738 #define TOP_RESIZE_HEIGHT 4
1739 #define CORNER_SIZE_MULT 2
1740 static MetaFrameControl
get_control(MetaUIFrame * frame,int root_x,int root_y)1741 get_control (MetaUIFrame *frame, int root_x, int root_y)
1742 {
1743 MetaFrameGeometry fgeom;
1744 MetaFrameFlags flags;
1745 MetaFrameType type;
1746 gboolean has_vert, has_horiz;
1747 gboolean has_north_resize;
1748 cairo_rectangle_int_t client;
1749 int x, y;
1750 int win_x, win_y;
1751
1752 if (meta_window_is_fullscreen (frame->meta_window))
1753 return META_FRAME_CONTROL_CLIENT_AREA;
1754
1755 gdk_window_get_position (frame->window, &win_x, &win_y);
1756 x = root_x - win_x;
1757 y = root_y - win_y;
1758
1759 meta_window_get_client_area_rect (frame->meta_window, &client);
1760 if (META_POINT_IN_RECT (x, y, client))
1761 return META_FRAME_CONTROL_CLIENT_AREA;
1762
1763 meta_ui_frame_calc_geometry (frame, &fgeom);
1764
1765 if (META_POINT_IN_RECT (x, y, fgeom.close_rect.clickable))
1766 return META_FRAME_CONTROL_DELETE;
1767
1768 if (META_POINT_IN_RECT (x, y, fgeom.min_rect.clickable))
1769 return META_FRAME_CONTROL_MINIMIZE;
1770
1771 if (META_POINT_IN_RECT (x, y, fgeom.menu_rect.clickable))
1772 return META_FRAME_CONTROL_MENU;
1773
1774 flags = meta_frame_get_flags (frame->meta_window->frame);
1775 type = meta_window_get_frame_type (frame->meta_window);
1776
1777 has_north_resize = (type != META_FRAME_TYPE_ATTACHED);
1778 has_vert = (flags & META_FRAME_ALLOWS_VERTICAL_RESIZE) != 0;
1779 has_horiz = (flags & META_FRAME_ALLOWS_HORIZONTAL_RESIZE) != 0;
1780
1781 if (flags & META_FRAME_TILED_LEFT || flags & META_FRAME_TILED_RIGHT)
1782 has_vert = has_horiz = FALSE;
1783
1784 if (META_POINT_IN_RECT (x, y, fgeom.title_rect))
1785 {
1786 if (has_vert && y <= TOP_RESIZE_HEIGHT && has_north_resize)
1787 return META_FRAME_CONTROL_RESIZE_N;
1788 else
1789 return META_FRAME_CONTROL_TITLE;
1790 }
1791
1792 if (META_POINT_IN_RECT (x, y, fgeom.max_rect.clickable))
1793 {
1794 if (flags & META_FRAME_MAXIMIZED)
1795 return META_FRAME_CONTROL_UNMAXIMIZE;
1796 else
1797 return META_FRAME_CONTROL_MAXIMIZE;
1798 }
1799
1800 /* South resize always has priority over north resize,
1801 * in case of overlap.
1802 */
1803
1804 if (y >= (fgeom.height - fgeom.borders.total.bottom * CORNER_SIZE_MULT) &&
1805 x >= (fgeom.width - fgeom.borders.total.right * CORNER_SIZE_MULT))
1806 {
1807 if (has_vert && has_horiz)
1808 return META_FRAME_CONTROL_RESIZE_SE;
1809 else if (has_vert)
1810 return META_FRAME_CONTROL_RESIZE_S;
1811 else if (has_horiz)
1812 return META_FRAME_CONTROL_RESIZE_E;
1813 }
1814 else if (y >= (fgeom.height - fgeom.borders.total.bottom * CORNER_SIZE_MULT) &&
1815 x <= fgeom.borders.total.left * CORNER_SIZE_MULT)
1816 {
1817 if (has_vert && has_horiz)
1818 return META_FRAME_CONTROL_RESIZE_SW;
1819 else if (has_vert)
1820 return META_FRAME_CONTROL_RESIZE_S;
1821 else if (has_horiz)
1822 return META_FRAME_CONTROL_RESIZE_W;
1823 }
1824 else if (y < (fgeom.borders.invisible.top * CORNER_SIZE_MULT) &&
1825 x <= (fgeom.borders.total.left * CORNER_SIZE_MULT) && has_north_resize)
1826 {
1827 if (has_vert && has_horiz)
1828 return META_FRAME_CONTROL_RESIZE_NW;
1829 else if (has_vert)
1830 return META_FRAME_CONTROL_RESIZE_N;
1831 else if (has_horiz)
1832 return META_FRAME_CONTROL_RESIZE_W;
1833 }
1834 else if (y < (fgeom.borders.invisible.top * CORNER_SIZE_MULT) &&
1835 x >= (fgeom.width - fgeom.borders.total.right * CORNER_SIZE_MULT) && has_north_resize)
1836 {
1837 if (has_vert && has_horiz)
1838 return META_FRAME_CONTROL_RESIZE_NE;
1839 else if (has_vert)
1840 return META_FRAME_CONTROL_RESIZE_N;
1841 else if (has_horiz)
1842 return META_FRAME_CONTROL_RESIZE_E;
1843 }
1844 else if (y < (fgeom.borders.invisible.top + TOP_RESIZE_HEIGHT))
1845 {
1846 if (has_vert && has_north_resize)
1847 return META_FRAME_CONTROL_RESIZE_N;
1848 }
1849 else if (y >= (fgeom.height - fgeom.borders.total.bottom))
1850 {
1851 if (has_vert)
1852 return META_FRAME_CONTROL_RESIZE_S;
1853 }
1854 else if (x <= fgeom.borders.total.left)
1855 {
1856 if (has_horiz || flags & META_FRAME_TILED_RIGHT)
1857 return META_FRAME_CONTROL_RESIZE_W;
1858 }
1859 else if (x >= (fgeom.width - fgeom.borders.total.right))
1860 {
1861 if (has_horiz || flags & META_FRAME_TILED_LEFT)
1862 return META_FRAME_CONTROL_RESIZE_E;
1863 }
1864
1865 if (y >= fgeom.borders.total.top)
1866 return META_FRAME_CONTROL_NONE;
1867 else
1868 return META_FRAME_CONTROL_TITLE;
1869 }
1870