blob: dc7ff85c4b4851d31bbf216c2db21895f78ef45f [file] [log] [blame]
/*
* Copyright © 2008 Kristian Høgsberg
* Copyright © 2012-2013 Collabora, Ltd.
* Copyright © 2013 Jason Ekstrand
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "config.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-util.h>
#include <linux/input.h>
#include "cairo-util.h"
enum frame_button_flags {
FRAME_BUTTON_ALIGN_RIGHT = 0x1,
FRAME_BUTTON_DECORATED = 0x2,
FRAME_BUTTON_CLICK_DOWN = 0x4,
};
struct frame_button {
struct frame *frame;
struct wl_list link; /* buttons_list */
cairo_surface_t *icon;
enum frame_button_flags flags;
int hover_count;
int press_count;
struct {
int x, y;
int width, height;
} allocation;
enum frame_status status_effect;
};
struct frame_pointer_button {
struct wl_list link;
uint32_t button;
enum theme_location press_location;
struct frame_button *frame_button;
};
struct frame_pointer {
struct wl_list link;
void *data;
int x, y;
struct frame_button *hover_button;
struct wl_list down_buttons;
};
struct frame_touch {
struct wl_list link;
void *data;
int x, y;
struct frame_button *button;
};
struct frame {
int32_t width, height;
char *title;
uint32_t flags;
struct theme *theme;
struct {
int32_t x, y;
int32_t width, height;
} interior;
int shadow_margin;
int opaque_margin;
int geometry_dirty;
cairo_rectangle_int_t title_rect;
uint32_t status;
struct wl_list buttons;
struct wl_list pointers;
struct wl_list touches;
};
static struct frame_button *
frame_button_create_from_surface(struct frame *frame, cairo_surface_t *icon,
enum frame_status status_effect,
enum frame_button_flags flags)
{
struct frame_button *button;
button = calloc(1, sizeof *button);
if (!button)
return NULL;
button->icon = icon;
button->frame = frame;
button->flags = flags;
button->status_effect = status_effect;
wl_list_insert(frame->buttons.prev, &button->link);
return button;
}
static struct frame_button *
frame_button_create(struct frame *frame, const char *icon_name,
enum frame_status status_effect,
enum frame_button_flags flags)
{
struct frame_button *button;
cairo_surface_t *icon;
icon = cairo_image_surface_create_from_png(icon_name);
if (cairo_surface_status(icon) != CAIRO_STATUS_SUCCESS)
goto error;
button = frame_button_create_from_surface(frame, icon, status_effect,
flags);
if (!button)
goto error;
return button;
error:
cairo_surface_destroy(icon);
return NULL;
}
static void
frame_button_destroy(struct frame_button *button)
{
cairo_surface_destroy(button->icon);
free(button);
}
static void
frame_button_enter(struct frame_button *button)
{
if (!button->hover_count)
button->frame->status |= FRAME_STATUS_REPAINT;
button->hover_count++;
}
static void
frame_button_leave(struct frame_button *button, struct frame_pointer *pointer)
{
button->hover_count--;
if (!button->hover_count)
button->frame->status |= FRAME_STATUS_REPAINT;
}
static void
frame_button_press(struct frame_button *button)
{
if (!button->press_count)
button->frame->status |= FRAME_STATUS_REPAINT;
button->press_count++;
if (button->flags & FRAME_BUTTON_CLICK_DOWN)
button->frame->status |= button->status_effect;
}
static void
frame_button_release(struct frame_button *button)
{
button->press_count--;
if (button->press_count)
return;
button->frame->status |= FRAME_STATUS_REPAINT;
if (!(button->flags & FRAME_BUTTON_CLICK_DOWN))
button->frame->status |= button->status_effect;
}
static void
frame_button_cancel(struct frame_button *button)
{
button->press_count--;
if (!button->press_count)
button->frame->status |= FRAME_STATUS_REPAINT;
}
static void
frame_button_repaint(struct frame_button *button, cairo_t *cr)
{
int x, y;
if (!button->allocation.width)
return;
if (!button->allocation.height)
return;
x = button->allocation.x;
y = button->allocation.y;
cairo_save(cr);
if (button->flags & FRAME_BUTTON_DECORATED) {
cairo_set_line_width(cr, 1);
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_rectangle(cr, x, y, 25, 16);
cairo_stroke_preserve(cr);
if (button->press_count) {
cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
} else if (button->hover_count) {
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
} else {
cairo_set_source_rgb(cr, 0.88, 0.88, 0.88);
}
cairo_fill (cr);
x += 4;
}
cairo_set_source_surface(cr, button->icon, x, y);
cairo_paint(cr);
cairo_restore(cr);
}
static struct frame_pointer *
frame_pointer_get(struct frame *frame, void *data)
{
struct frame_pointer *pointer;
wl_list_for_each(pointer, &frame->pointers, link)
if (pointer->data == data)
return pointer;
pointer = calloc(1, sizeof *pointer);
if (!pointer)
return NULL;
pointer->data = data;
wl_list_init(&pointer->down_buttons);
wl_list_insert(&frame->pointers, &pointer->link);
return pointer;
}
static void
frame_pointer_destroy(struct frame_pointer *pointer)
{
wl_list_remove(&pointer->link);
free(pointer);
}
static struct frame_touch *
frame_touch_get(struct frame *frame, void *data)
{
struct frame_touch *touch;
wl_list_for_each(touch, &frame->touches, link)
if (touch->data == data)
return touch;
touch = calloc(1, sizeof *touch);
if (!touch)
return NULL;
touch->data = data;
wl_list_insert(&frame->touches, &touch->link);
return touch;
}
static void
frame_touch_destroy(struct frame_touch *touch)
{
wl_list_remove(&touch->link);
free(touch);
}
void
frame_destroy(struct frame *frame)
{
struct frame_button *button, *next;
struct frame_touch *touch, *next_touch;
struct frame_pointer *pointer, *next_pointer;
wl_list_for_each_safe(button, next, &frame->buttons, link)
frame_button_destroy(button);
wl_list_for_each_safe(touch, next_touch, &frame->touches, link)
frame_touch_destroy(touch);
wl_list_for_each_safe(pointer, next_pointer, &frame->pointers, link)
frame_pointer_destroy(pointer);
free(frame->title);
free(frame);
}
struct frame *
frame_create(struct theme *t, int32_t width, int32_t height, uint32_t buttons,
const char *title, cairo_surface_t *icon)
{
struct frame *frame;
struct frame_button *button;
frame = calloc(1, sizeof *frame);
if (!frame)
return NULL;
frame->width = width;
frame->height = height;
frame->flags = 0;
frame->theme = t;
frame->status = FRAME_STATUS_REPAINT;
frame->geometry_dirty = 1;
wl_list_init(&frame->buttons);
wl_list_init(&frame->pointers);
wl_list_init(&frame->touches);
if (title) {
frame->title = strdup(title);
if (!frame->title)
goto free_frame;
}
if (title) {
if (icon) {
button = frame_button_create_from_surface(frame,
icon,
FRAME_STATUS_MENU,
FRAME_BUTTON_CLICK_DOWN);
} else {
button = frame_button_create(frame,
DATADIR "/weston/icon_window.png",
FRAME_STATUS_MENU,
FRAME_BUTTON_CLICK_DOWN);
}
if (!button)
goto free_frame;
}
if (buttons & FRAME_BUTTON_CLOSE) {
button = frame_button_create(frame,
DATADIR "/weston/sign_close.png",
FRAME_STATUS_CLOSE,
FRAME_BUTTON_ALIGN_RIGHT |
FRAME_BUTTON_DECORATED);
if (!button)
goto free_frame;
}
if (buttons & FRAME_BUTTON_MAXIMIZE) {
button = frame_button_create(frame,
DATADIR "/weston/sign_maximize.png",
FRAME_STATUS_MAXIMIZE,
FRAME_BUTTON_ALIGN_RIGHT |
FRAME_BUTTON_DECORATED);
if (!button)
goto free_frame;
}
if (buttons & FRAME_BUTTON_MINIMIZE) {
button = frame_button_create(frame,
DATADIR "/weston/sign_minimize.png",
FRAME_STATUS_MINIMIZE,
FRAME_BUTTON_ALIGN_RIGHT |
FRAME_BUTTON_DECORATED);
if (!button)
goto free_frame;
}
return frame;
free_frame:
frame_destroy(frame);
return NULL;
}
int
frame_set_title(struct frame *frame, const char *title)
{
char *dup = NULL;
if (title) {
dup = strdup(title);
if (!dup)
return -1;
}
free(frame->title);
frame->title = dup;
frame->geometry_dirty = 1;
frame->status |= FRAME_STATUS_REPAINT;
return 0;
}
void
frame_set_flag(struct frame *frame, enum frame_flag flag)
{
if (flag & FRAME_FLAG_MAXIMIZED && !(frame->flags & FRAME_FLAG_MAXIMIZED))
frame->geometry_dirty = 1;
frame->flags |= flag;
frame->status |= FRAME_STATUS_REPAINT;
}
void
frame_unset_flag(struct frame *frame, enum frame_flag flag)
{
if (flag & FRAME_FLAG_MAXIMIZED && frame->flags & FRAME_FLAG_MAXIMIZED)
frame->geometry_dirty = 1;
frame->flags &= ~flag;
frame->status |= FRAME_STATUS_REPAINT;
}
void
frame_resize(struct frame *frame, int32_t width, int32_t height)
{
frame->width = width;
frame->height = height;
frame->geometry_dirty = 1;
frame->status |= FRAME_STATUS_REPAINT;
}
void
frame_resize_inside(struct frame *frame, int32_t width, int32_t height)
{
struct theme *t = frame->theme;
int decoration_width, decoration_height, titlebar_height;
if (frame->title || !wl_list_empty(&frame->buttons))
titlebar_height = t->titlebar_height;
else
titlebar_height = t->width;
if (frame->flags & FRAME_FLAG_MAXIMIZED) {
decoration_width = t->width * 2;
decoration_height = t->width + titlebar_height;
} else {
decoration_width = (t->width + t->margin) * 2;
decoration_height = t->width +
titlebar_height + t->margin * 2;
}
frame_resize(frame, width + decoration_width,
height + decoration_height);
}
int32_t
frame_width(struct frame *frame)
{
return frame->width;
}
int32_t
frame_height(struct frame *frame)
{
return frame->height;
}
static void
frame_refresh_geometry(struct frame *frame)
{
struct frame_button *button;
struct theme *t = frame->theme;
int x_l, x_r, y, w, h, titlebar_height;
int32_t decoration_width, decoration_height;
if (!frame->geometry_dirty)
return;
if (frame->title || !wl_list_empty(&frame->buttons))
titlebar_height = t->titlebar_height;
else
titlebar_height = t->width;
if (frame->flags & FRAME_FLAG_MAXIMIZED) {
decoration_width = t->width * 2;
decoration_height = t->width + titlebar_height;
frame->interior.x = t->width;
frame->interior.y = titlebar_height;
frame->interior.width = frame->width - decoration_width;
frame->interior.height = frame->height - decoration_height;
frame->opaque_margin = 0;
frame->shadow_margin = 0;
} else {
decoration_width = (t->width + t->margin) * 2;
decoration_height = t->width + titlebar_height + t->margin * 2;
frame->interior.x = t->width + t->margin;
frame->interior.y = titlebar_height + t->margin;
frame->interior.width = frame->width - decoration_width;
frame->interior.height = frame->height - decoration_height;
frame->opaque_margin = t->margin + t->frame_radius;
frame->shadow_margin = t->margin;
}
x_r = frame->width - t->width - frame->shadow_margin;
x_l = t->width + frame->shadow_margin;
y = t->width + frame->shadow_margin;
wl_list_for_each(button, &frame->buttons, link) {
const int button_padding = 4;
w = cairo_image_surface_get_width(button->icon);
h = cairo_image_surface_get_height(button->icon);
if (button->flags & FRAME_BUTTON_DECORATED)
w += 10;
if (button->flags & FRAME_BUTTON_ALIGN_RIGHT) {
x_r -= w;
button->allocation.x = x_r;
button->allocation.y = y;
button->allocation.width = w + 1;
button->allocation.height = h + 1;
x_r -= button_padding;
} else {
button->allocation.x = x_l;
button->allocation.y = y;
button->allocation.width = w + 1;
button->allocation.height = h + 1;
x_l += w;
x_l += button_padding;
}
}
frame->title_rect.x = x_l;
frame->title_rect.y = y;
frame->title_rect.width = x_r - x_l;
frame->title_rect.height = titlebar_height;
frame->geometry_dirty = 0;
}
void
frame_interior(struct frame *frame, int32_t *x, int32_t *y,
int32_t *width, int32_t *height)
{
frame_refresh_geometry(frame);
if (x)
*x = frame->interior.x;
if (y)
*y = frame->interior.y;
if (width)
*width = frame->interior.width;
if (height)
*height = frame->interior.height;
}
void
frame_input_rect(struct frame *frame, int32_t *x, int32_t *y,
int32_t *width, int32_t *height)
{
frame_refresh_geometry(frame);
if (x)
*x = frame->shadow_margin;
if (y)
*y = frame->shadow_margin;
if (width)
*width = frame->width - frame->shadow_margin * 2;
if (height)
*height = frame->height - frame->shadow_margin * 2;
}
void
frame_opaque_rect(struct frame *frame, int32_t *x, int32_t *y,
int32_t *width, int32_t *height)
{
frame_refresh_geometry(frame);
if (x)
*x = frame->opaque_margin;
if (y)
*y = frame->opaque_margin;
if (width)
*width = frame->width - frame->opaque_margin * 2;
if (height)
*height = frame->height - frame->opaque_margin * 2;
}
int
frame_get_shadow_margin(struct frame *frame)
{
frame_refresh_geometry(frame);
return frame->shadow_margin;
}
uint32_t
frame_status(struct frame *frame)
{
return frame->status;
}
void
frame_status_clear(struct frame *frame, enum frame_status status)
{
frame->status &= ~status;
}
static struct frame_button *
frame_find_button(struct frame *frame, int x, int y)
{
struct frame_button *button;
int rel_x, rel_y;
wl_list_for_each(button, &frame->buttons, link) {
rel_x = x - button->allocation.x;
rel_y = y - button->allocation.y;
if (0 <= rel_x && rel_x < button->allocation.width &&
0 <= rel_y && rel_y < button->allocation.height)
return button;
}
return NULL;
}
enum theme_location
frame_pointer_enter(struct frame *frame, void *data, int x, int y)
{
return frame_pointer_motion(frame, data, x, y);
}
enum theme_location
frame_pointer_motion(struct frame *frame, void *data, int x, int y)
{
struct frame_pointer *pointer = frame_pointer_get(frame, data);
struct frame_button *button = frame_find_button(frame, x, y);
enum theme_location location;
location = theme_get_location(frame->theme, x, y,
frame->width, frame->height,
frame->flags & FRAME_FLAG_MAXIMIZED ?
THEME_FRAME_MAXIMIZED : 0);
if (!pointer)
return location;
pointer->x = x;
pointer->y = y;
if (pointer->hover_button == button)
return location;
if (pointer->hover_button)
frame_button_leave(pointer->hover_button, pointer);
pointer->hover_button = button;
if (pointer->hover_button)
frame_button_enter(pointer->hover_button);
return location;
}
static void
frame_pointer_button_destroy(struct frame_pointer_button *button)
{
wl_list_remove(&button->link);
free(button);
}
static void
frame_pointer_button_press(struct frame *frame, struct frame_pointer *pointer,
struct frame_pointer_button *button)
{
if (button->button == BTN_RIGHT) {
if (button->press_location == THEME_LOCATION_TITLEBAR)
frame->status |= FRAME_STATUS_MENU;
frame_pointer_button_destroy(button);
} else if (button->button == BTN_LEFT) {
if (pointer->hover_button) {
frame_button_press(pointer->hover_button);
} else {
switch (button->press_location) {
case THEME_LOCATION_TITLEBAR:
frame->status |= FRAME_STATUS_MOVE;
frame_pointer_button_destroy(button);
break;
case THEME_LOCATION_RESIZING_TOP:
case THEME_LOCATION_RESIZING_BOTTOM:
case THEME_LOCATION_RESIZING_LEFT:
case THEME_LOCATION_RESIZING_RIGHT:
case THEME_LOCATION_RESIZING_TOP_LEFT:
case THEME_LOCATION_RESIZING_TOP_RIGHT:
case THEME_LOCATION_RESIZING_BOTTOM_LEFT:
case THEME_LOCATION_RESIZING_BOTTOM_RIGHT:
frame->status |= FRAME_STATUS_RESIZE;
frame_pointer_button_destroy(button);
break;
default:
break;
}
}
}
}
static void
frame_pointer_button_release(struct frame *frame, struct frame_pointer *pointer,
struct frame_pointer_button *button)
{
if (button->button == BTN_LEFT && button->frame_button) {
if (button->frame_button == pointer->hover_button)
frame_button_release(button->frame_button);
else
frame_button_cancel(button->frame_button);
}
}
static void
frame_pointer_button_cancel(struct frame *frame, struct frame_pointer *pointer,
struct frame_pointer_button *button)
{
if (button->frame_button)
frame_button_cancel(button->frame_button);
}
void
frame_pointer_leave(struct frame *frame, void *data)
{
struct frame_pointer *pointer = frame_pointer_get(frame, data);
struct frame_pointer_button *button, *next;
if (!pointer)
return;
if (pointer->hover_button)
frame_button_leave(pointer->hover_button, pointer);
wl_list_for_each_safe(button, next, &pointer->down_buttons, link) {
frame_pointer_button_cancel(frame, pointer, button);
frame_pointer_button_destroy(button);
}
frame_pointer_destroy(pointer);
}
enum theme_location
frame_pointer_button(struct frame *frame, void *data,
uint32_t btn, enum wl_pointer_button_state state)
{
struct frame_pointer *pointer = frame_pointer_get(frame, data);
struct frame_pointer_button *button;
enum theme_location location = THEME_LOCATION_EXTERIOR;
if (!pointer)
return location;
location = theme_get_location(frame->theme, pointer->x, pointer->y,
frame->width, frame->height,
frame->flags & FRAME_FLAG_MAXIMIZED ?
THEME_FRAME_MAXIMIZED : 0);
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
button = malloc(sizeof *button);
if (!button)
return location;
button->button = btn;
button->press_location = location;
button->frame_button = pointer->hover_button;
wl_list_insert(&pointer->down_buttons, &button->link);
frame_pointer_button_press(frame, pointer, button);
} else if (state == WL_POINTER_BUTTON_STATE_RELEASED) {
button = NULL;
wl_list_for_each(button, &pointer->down_buttons, link)
if (button->button == btn)
break;
/* Make sure we didn't hit the end */
if (&button->link == &pointer->down_buttons)
return location;
location = button->press_location;
frame_pointer_button_release(frame, pointer, button);
frame_pointer_button_destroy(button);
}
return location;
}
enum theme_location
frame_touch_down(struct frame *frame, void *data, int32_t id, int x, int y)
{
struct frame_touch *touch = frame_touch_get(frame, data);
struct frame_button *button = frame_find_button(frame, x, y);
enum theme_location location;
location = theme_get_location(frame->theme, x, y,
frame->width, frame->height,
frame->flags & FRAME_FLAG_MAXIMIZED ?
THEME_FRAME_MAXIMIZED : 0);
if (id > 0)
return location;
if (touch && button) {
touch->button = button;
frame_button_press(touch->button);
return location;
}
switch (location) {
case THEME_LOCATION_TITLEBAR:
frame->status |= FRAME_STATUS_MOVE;
break;
case THEME_LOCATION_RESIZING_TOP:
case THEME_LOCATION_RESIZING_BOTTOM:
case THEME_LOCATION_RESIZING_LEFT:
case THEME_LOCATION_RESIZING_RIGHT:
case THEME_LOCATION_RESIZING_TOP_LEFT:
case THEME_LOCATION_RESIZING_TOP_RIGHT:
case THEME_LOCATION_RESIZING_BOTTOM_LEFT:
case THEME_LOCATION_RESIZING_BOTTOM_RIGHT:
frame->status |= FRAME_STATUS_RESIZE;
break;
default:
break;
}
return location;
}
void
frame_touch_up(struct frame *frame, void *data, int32_t id)
{
struct frame_touch *touch = frame_touch_get(frame, data);
if (id > 0)
return;
if (touch && touch->button) {
frame_button_release(touch->button);
frame_touch_destroy(touch);
}
}
enum theme_location
frame_double_click(struct frame *frame, void *data,
uint32_t btn, enum wl_pointer_button_state state)
{
struct frame_pointer *pointer = frame_pointer_get(frame, data);
struct frame_button *button;
enum theme_location location = THEME_LOCATION_EXTERIOR;
location = theme_get_location(frame->theme, pointer->x, pointer->y,
frame->width, frame->height,
frame->flags & FRAME_FLAG_MAXIMIZED ?
THEME_FRAME_MAXIMIZED : 0);
button = frame_find_button(frame, pointer->x, pointer->y);
if (location != THEME_LOCATION_TITLEBAR || btn != BTN_LEFT)
return location;
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
if (button)
frame_button_press(button);
else
frame->status |= FRAME_STATUS_MAXIMIZE;
} else if (state == WL_POINTER_BUTTON_STATE_RELEASED) {
if (button)
frame_button_release(button);
}
return location;
}
void
frame_double_touch_down(struct frame *frame, void *data, int32_t id,
int x, int y)
{
struct frame_touch *touch = frame_touch_get(frame, data);
struct frame_button *button = frame_find_button(frame, x, y);
enum theme_location location;
if (touch && button) {
touch->button = button;
frame_button_press(touch->button);
return;
}
location = theme_get_location(frame->theme, x, y,
frame->width, frame->height,
frame->flags & FRAME_FLAG_MAXIMIZED ?
THEME_FRAME_MAXIMIZED : 0);
switch (location) {
case THEME_LOCATION_TITLEBAR:
frame->status |= FRAME_STATUS_MAXIMIZE;
break;
case THEME_LOCATION_RESIZING_TOP:
case THEME_LOCATION_RESIZING_BOTTOM:
case THEME_LOCATION_RESIZING_LEFT:
case THEME_LOCATION_RESIZING_RIGHT:
case THEME_LOCATION_RESIZING_TOP_LEFT:
case THEME_LOCATION_RESIZING_TOP_RIGHT:
case THEME_LOCATION_RESIZING_BOTTOM_LEFT:
case THEME_LOCATION_RESIZING_BOTTOM_RIGHT:
frame->status |= FRAME_STATUS_RESIZE;
break;
default:
break;
}
}
void
frame_double_touch_up(struct frame *frame, void *data, int32_t id)
{
struct frame_touch *touch = frame_touch_get(frame, data);
if (touch && touch->button) {
frame_button_release(touch->button);
frame_touch_destroy(touch);
}
}
void
frame_repaint(struct frame *frame, cairo_t *cr)
{
struct frame_button *button;
uint32_t flags = 0;
frame_refresh_geometry(frame);
if (frame->flags & FRAME_FLAG_MAXIMIZED)
flags |= THEME_FRAME_MAXIMIZED;
if (frame->flags & FRAME_FLAG_ACTIVE)
flags |= THEME_FRAME_ACTIVE;
cairo_save(cr);
theme_render_frame(frame->theme, cr, frame->width, frame->height,
frame->title, &frame->title_rect,
&frame->buttons, flags);
cairo_restore(cr);
wl_list_for_each(button, &frame->buttons, link)
frame_button_repaint(button, cr);
frame_status_clear(frame, FRAME_STATUS_REPAINT);
}