mutter/src/compositor/meta-background.c

1408 lines
46 KiB
C
Raw Normal View History

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright 2013 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
/**
* SECTION:meta-background
* @title: MetaBackground
* @short_description: ClutterContent for painting the system background
*
*/
#include <config.h>
#include <cogl/cogl-texture-pixmap-x11.h>
#include <clutter/clutter.h>
#include "cogl-utils.h"
#include "compositor-private.h"
#include "mutter-enum-types.h"
#include <meta/errors.h>
#include <meta/meta-background.h>
#include "meta-background-actor-private.h"
#define TEXTURE_LOOKUP_SHADER_DECLARATIONS \
"uniform vec2 pixel_step;\n" \
"vec4 apply_blur(in sampler2D texture, in vec2 coordinates) {\n" \
" vec4 texel;\n" \
" texel = texture2D(texture, coordinates.st);\n" \
" texel += texture2D(texture, coordinates.st + pixel_step * vec2(-1.0, -1.0));\n"\
" texel += texture2D(texture, coordinates.st + pixel_step * vec2( 0.0, -1.0));\n"\
" texel += texture2D(texture, coordinates.st + pixel_step * vec2(+1.0, -1.0));\n"\
" texel += texture2D(texture, coordinates.st + pixel_step * vec2(-1.0, 0.0));\n"\
" texel += texture2D(texture, coordinates.st + pixel_step * vec2(+1.0, 0.0));\n"\
" texel += texture2D(texture, coordinates.st + pixel_step * vec2(-1.0, +1.0));\n"\
" texel += texture2D(texture, coordinates.st + pixel_step * vec2( 0.0, +1.0));\n"\
" texel += texture2D(texture, coordinates.st + pixel_step * vec2(+1.0, +1.0));\n"\
" texel /= 9.0;\n" \
" return texel;\n" \
"}\n" \
"uniform float saturation;\n" \
"vec3 desaturate(const vec3 color)\n" \
"{\n" \
" const vec3 gray_conv = vec3(0.299, 0.587, 0.114);\n" \
" vec3 gray = vec3(dot(gray_conv, color));\n" \
" return vec3(mix(color.rgb, gray, 1.0 - saturation));\n" \
"}\n" \
/* Used when we don't have a blur, as the texel is going to be junk
* unless we set something to it. */
#define DESATURATE_PRELUDE \
"cogl_texel = texture2D(cogl_sampler, cogl_tex_coord.st);\n"
#define DESATURATE_CODE \
"cogl_texel.rgb = desaturate(cogl_texel.rgb);\n"
#define BLUR_CODE \
"cogl_texel = apply_blur(cogl_sampler, cogl_tex_coord.st);\n"
#define FRAGMENT_SHADER_DECLARATIONS \
"uniform vec2 texture_scale;\n" \
"uniform vec2 actor_size;\n" \
"uniform vec2 offset;\n" \
"uniform float brightness;\n" \
"uniform float vignette_sharpness;\n" \
#define VIGNETTE_CODE \
"vec2 position = cogl_tex_coord_in[0].xy * texture_scale - offset;\n" \
"float t = length(2.0 * (position / actor_size));\n" \
"t = clamp(t, 0.0, 1.0);\n" \
"float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t);\n" \
"cogl_color_out.rgb = cogl_color_out.rgb * pixel_brightness * brightness;\n"
/* We allow creating multiple MetaBackgrounds for the same monitor to
* allow different rendering options to be set for different copies.
* But we want to share the same underlying CoglTextures for efficiency and
* to avoid driver bugs that might occur if we created multiple CoglTexturePixmaps
* for the same pixmap.
*
* This object provides a ClutterContent object to assist in sharing between actors.
*/
typedef struct _MetaBackgroundPrivate MetaBackgroundPrivate;
struct _MetaBackgroundPrivate
{
MetaScreen *screen;
CoglTexture *texture;
CoglPipeline *pipeline;
int monitor;
MetaBackgroundEffects effects;
GDesktopBackgroundStyle style;
GDesktopBackgroundShading shading_direction;
ClutterColor color;
ClutterColor second_color;
char *filename;
float brightness;
float vignette_sharpness;
float saturation;
};
enum
{
PROP_META_SCREEN = 1,
PROP_MONITOR,
PROP_EFFECTS,
PROP_BRIGHTNESS,
PROP_VIGNETTE_SHARPNESS,
PROP_SATURATION
};
static void clutter_content_iface_init (ClutterContentIface *iface);
static void unset_texture (MetaBackground *self);
G_DEFINE_TYPE_WITH_CODE (MetaBackground, meta_background, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
clutter_content_iface_init))
static gboolean
meta_background_get_preferred_size (ClutterContent *content,
gfloat *width,
gfloat *height)
{
MetaBackgroundPrivate *priv = META_BACKGROUND (content)->priv;
MetaRectangle monitor_geometry;
if (priv->texture == NULL)
return FALSE;
meta_screen_get_monitor_geometry (priv->screen, priv->monitor, &monitor_geometry);
if (width != NULL)
*width = monitor_geometry.width;
if (height != NULL)
*height = monitor_geometry.height;
return TRUE;
}
static void
get_texture_area_and_scale (MetaBackground *self,
ClutterActorBox *actor_box,
cairo_rectangle_int_t *texture_area,
float *texture_x_scale,
float *texture_y_scale)
{
MetaBackgroundPrivate *priv = self->priv;
MetaRectangle monitor_geometry;
cairo_rectangle_int_t actor_pixel_rect;
cairo_rectangle_int_t image_area;
int screen_width, screen_height;
float texture_width, texture_height;
float actor_x_scale, actor_y_scale;
float monitor_x_scale, monitor_y_scale;
float x_offset, y_offset;
meta_screen_get_monitor_geometry (priv->screen, priv->monitor, &monitor_geometry);
actor_pixel_rect.x = actor_box->x1;
actor_pixel_rect.y = actor_box->y1;
actor_pixel_rect.width = actor_box->x2 - actor_box->x1;
actor_pixel_rect.height = actor_box->y2 - actor_box->y1;
texture_width = cogl_texture_get_width (priv->texture);
actor_x_scale = (1.0 * actor_pixel_rect.width / monitor_geometry.width);
texture_height = cogl_texture_get_height (priv->texture);
actor_y_scale = (1.0 * actor_pixel_rect.height / monitor_geometry.height);
switch (priv->style)
{
case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
default:
/* paint region is whole actor, and the texture
* is scaled disproportionately to fit the actor
*/
*texture_area = actor_pixel_rect;
*texture_x_scale = 1.0 / actor_pixel_rect.width;
*texture_y_scale = 1.0 / actor_pixel_rect.height;
break;
case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
/* The wallpaper should be centered in the middle of all monitors.
* Therefore, the textured area is the union of all monitors plus
* an additional bit to make up for the texture getting centered. */
meta_screen_get_size (priv->screen, &screen_width, &screen_height);
/* so start by making the unclipped texture area the whole screen */
image_area.width = screen_width;
image_area.height = screen_height;
/* If one of the tiles is already centered in the screen, then that tile
* will start tile_size/2.0 before the center of the screen. So find out
* how far we are from that ideal and adjust by that offset.
*/
x_offset = texture_width - ((int) ((screen_width / 2.0) - (texture_width / 2.0))) % ((int) texture_width);
y_offset = texture_height - ((int) ((screen_height / 2.0) - (texture_height / 2.0))) % ((int) texture_height);
image_area.width += x_offset;
image_area.height += y_offset;
image_area.x = -x_offset;
image_area.y = -y_offset;
/* now line up with the appropriate monitor */
image_area.x -= monitor_geometry.x;
image_area.y -= monitor_geometry.y;
/* and scale to actor */
image_area.x *= actor_x_scale;
image_area.y *= actor_y_scale;
image_area.width *= actor_x_scale;
image_area.height *= actor_y_scale;
*texture_area = image_area;
*texture_x_scale = 1.0 / texture_width;
*texture_y_scale = 1.0 / texture_height;
break;
case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
/* paint region is the original image size centered in the actor,
* and the texture is scaled to the original image size */
image_area.width = texture_width;
image_area.height = texture_height;
image_area.x = actor_pixel_rect.x + actor_pixel_rect.width / 2 - image_area.width / 2;
image_area.y = actor_pixel_rect.y + actor_pixel_rect.height / 2 - image_area.height / 2;
*texture_area = image_area;
*texture_x_scale = 1.0 / texture_width;
*texture_y_scale = 1.0 / texture_height;
break;
case G_DESKTOP_BACKGROUND_STYLE_SCALED:
case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
/* paint region is the actor size in one dimension, and centered and
* scaled by proportional amount in the other dimension.
*
* SCALED forces the centered dimension to fit on screen.
* ZOOM forces the centered dimension to grow off screen
*/
monitor_x_scale = monitor_geometry.width / texture_width;
monitor_y_scale = monitor_geometry.height / texture_height;
if ((priv->style == G_DESKTOP_BACKGROUND_STYLE_SCALED &&
(monitor_x_scale < monitor_y_scale)) ||
(priv->style == G_DESKTOP_BACKGROUND_STYLE_ZOOM &&
(monitor_x_scale > monitor_y_scale)))
{
/* Fill image to exactly fit actor horizontally */
image_area.width = actor_pixel_rect.width;
image_area.height = texture_height * monitor_x_scale * actor_y_scale;
/* Position image centered vertically in actor */
image_area.x = actor_pixel_rect.x;
image_area.y = actor_pixel_rect.y + actor_pixel_rect.height / 2 - image_area.height / 2;
}
else
{
/* Scale image to exactly fit actor vertically */
image_area.width = texture_width * monitor_y_scale * actor_x_scale;
image_area.height = actor_pixel_rect.height;
/* Position image centered horizontally in actor */
image_area.x = actor_pixel_rect.x + actor_pixel_rect.width / 2 - image_area.width / 2;
image_area.y = actor_pixel_rect.y;
}
*texture_area = image_area;
*texture_x_scale = 1.0 / image_area.width;
*texture_y_scale = 1.0 / image_area.height;
break;
case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
{
/* paint region is the union of all monitors, with the origin
* of the region set to align with monitor associated with the background.
*/
meta_screen_get_size (priv->screen, &screen_width, &screen_height);
/* unclipped texture area is whole screen */
image_area.width = screen_width * actor_x_scale;
image_area.height = screen_height * actor_y_scale;
/* But make (0,0) line up with the appropriate monitor */
image_area.x = -monitor_geometry.x * actor_x_scale;
image_area.y = -monitor_geometry.y * actor_y_scale;
*texture_area = image_area;
*texture_x_scale = 1.0 / image_area.width;
*texture_y_scale = 1.0 / image_area.height;
break;
}
}
}
static CoglPipelineWrapMode
get_wrap_mode (MetaBackground *self)
{
MetaBackgroundPrivate *priv = self->priv;
switch (priv->style)
{
case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
return COGL_PIPELINE_WRAP_MODE_REPEAT;
case G_DESKTOP_BACKGROUND_STYLE_NONE:
case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
case G_DESKTOP_BACKGROUND_STYLE_SCALED:
case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
default:
return COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
}
}
static ClutterPaintNode *
meta_background_paint_node_new (MetaBackground *self,
ClutterActor *actor)
{
MetaBackgroundPrivate *priv = self->priv;
ClutterPaintNode *node;
guint8 opacity;
guint8 color_component;
opacity = clutter_actor_get_paint_opacity (actor);
color_component = (guint8) (0.5 + opacity * priv->brightness);
cogl_pipeline_set_color4ub (priv->pipeline,
color_component,
color_component,
color_component,
opacity);
node = clutter_pipeline_node_new (priv->pipeline);
return node;
}
static void
clip_region_to_actor_box (cairo_region_t *region,
ClutterActorBox *actor_box)
{
cairo_rectangle_int_t clip_rect;
clip_rect.x = actor_box->x1;
clip_rect.y = actor_box->y1;
clip_rect.width = actor_box->x2 - actor_box->x1;
clip_rect.height = actor_box->y2 - actor_box->y1;
cairo_region_intersect_rectangle (region, &clip_rect);
}
static void
set_blur_parameters (MetaBackground *self,
ClutterActorBox *actor_box)
{
MetaBackgroundPrivate *priv = self->priv;
float pixel_step[2];
if (!(priv->effects & META_BACKGROUND_EFFECTS_BLUR))
return;
pixel_step[0] = 1.0 / (actor_box->x2 - actor_box->x1);
pixel_step[1] = 1.0 / (actor_box->y2 - actor_box->y1);
cogl_pipeline_set_uniform_float (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"pixel_step"),
2, 1, pixel_step);
}
static void
set_vignette_parameters (MetaBackground *self,
ClutterActorBox *actor_box,
cairo_rectangle_int_t *texture_area,
float texture_x_scale,
float texture_y_scale)
{
MetaBackgroundPrivate *priv = self->priv;
float texture_scale[2];
float actor_size[2];
float offset[2];
if (!(priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE))
return;
texture_scale[0] = 1.0 / texture_x_scale;
texture_scale[1] = 1.0 / texture_y_scale;
actor_size[0] = actor_box->x2 - actor_box->x1;
actor_size[1] = actor_box->y2 - actor_box->y1;
offset[0] = -texture_area->x + (actor_size[0] / 2.0);
offset[1] = -texture_area->y + (actor_size[1] / 2.0);
cogl_pipeline_set_uniform_float (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"texture_scale"),
2, 1, texture_scale);
cogl_pipeline_set_uniform_float (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"actor_size"),
2, 1, actor_size);
cogl_pipeline_set_uniform_float (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"offset"),
2, 1, offset);
}
static void
meta_background_paint_content (ClutterContent *content,
ClutterActor *actor,
ClutterPaintNode *root)
{
MetaBackground *self = META_BACKGROUND (content);
MetaBackgroundPrivate *priv = self->priv;
ClutterPaintNode *node;
ClutterActorBox actor_box;
cairo_rectangle_int_t texture_area;
cairo_region_t *paintable_region = NULL;
int n_texture_subareas;
int i;
float texture_x_scale, texture_y_scale;
float tx1 = 0.0, ty1 = 0.0, tx2 = 1.0, ty2 = 1.0;
if (priv->texture == NULL)
return;
node = meta_background_paint_node_new (self, actor);
clutter_actor_get_content_box (actor, &actor_box);
set_blur_parameters (self, &actor_box);
/* First figure out where on the monitor the texture is supposed to be painted.
* If the actor is not the size of the monitor, this function makes sure to scale
* everything down to fit in the actor.
*/
get_texture_area_and_scale (self,
&actor_box,
&texture_area,
&texture_x_scale,
&texture_y_scale);
set_vignette_parameters (self, &actor_box, &texture_area, texture_x_scale, texture_y_scale);
/* Now figure out what to actually paint. We start by clipping the texture area to
* the actor's bounds.
*/
paintable_region = cairo_region_create_rectangle (&texture_area);
clip_region_to_actor_box (paintable_region, &actor_box);
/* And then cut out any parts occluded by window actors
*/
if (META_IS_BACKGROUND_ACTOR (actor))
{
cairo_region_t *visible_region;
visible_region = meta_background_actor_get_visible_region (META_BACKGROUND_ACTOR (actor));
if (visible_region != NULL)
{
cairo_region_intersect (paintable_region, visible_region);
cairo_region_destroy (visible_region);
}
}
/* Finally, split the paintable region up into distinct areas
* and paint each area one by one
*/
n_texture_subareas = cairo_region_num_rectangles (paintable_region);
for (i = 0; i < n_texture_subareas; i++)
{
cairo_rectangle_int_t texture_subarea;
ClutterActorBox texture_rectangle;
cairo_region_get_rectangle (paintable_region, i, &texture_subarea);
tx1 = (texture_subarea.x - texture_area.x) * texture_x_scale;
ty1 = (texture_subarea.y - texture_area.y) * texture_y_scale;
tx2 = (texture_subarea.x + texture_subarea.width - texture_area.x) * texture_x_scale;
ty2 = (texture_subarea.y + texture_subarea.height - texture_area.y) * texture_y_scale;
texture_rectangle.x1 = texture_subarea.x;
texture_rectangle.y1 = texture_subarea.y;
texture_rectangle.x2 = texture_subarea.x + texture_subarea.width;
texture_rectangle.y2 = texture_subarea.y + texture_subarea.height;
clutter_paint_node_add_texture_rectangle (node, &texture_rectangle, tx1, ty1, tx2, ty2);
}
cairo_region_destroy (paintable_region);
clutter_paint_node_add_child (root, node);
clutter_paint_node_unref (node);
}
static void
clutter_content_iface_init (ClutterContentIface *iface)
{
iface->get_preferred_size = meta_background_get_preferred_size;
iface->paint_content = meta_background_paint_content;
}
static void
meta_background_dispose (GObject *object)
{
MetaBackground *self = META_BACKGROUND (object);
MetaBackgroundPrivate *priv = self->priv;
unset_texture (self);
g_clear_pointer (&priv->pipeline,
(GDestroyNotify)
cogl_object_unref);
G_OBJECT_CLASS (meta_background_parent_class)->dispose (object);
}
static void
ensure_pipeline (MetaBackground *self)
{
if (self->priv->pipeline == NULL)
self->priv->pipeline = COGL_PIPELINE (meta_create_texture_pipeline (NULL));
}
static void
set_brightness (MetaBackground *self,
gfloat brightness)
{
MetaBackgroundPrivate *priv = self->priv;
if (priv->brightness == brightness)
return;
priv->brightness = brightness;
if (priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE)
{
ensure_pipeline (self);
cogl_pipeline_set_uniform_1f (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"brightness"),
priv->brightness);
}
clutter_content_invalidate (CLUTTER_CONTENT (self));
g_object_notify (G_OBJECT (self), "brightness");
}
static void
set_vignette_sharpness (MetaBackground *self,
gfloat sharpness)
{
MetaBackgroundPrivate *priv = self->priv;
if (priv->vignette_sharpness == sharpness)
return;
priv->vignette_sharpness = sharpness;
if (priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE)
{
ensure_pipeline (self);
cogl_pipeline_set_uniform_1f (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"vignette_sharpness"),
priv->vignette_sharpness);
}
clutter_content_invalidate (CLUTTER_CONTENT (self));
g_object_notify (G_OBJECT (self), "vignette-sharpness");
}
static void
set_saturation (MetaBackground *self,
gfloat saturation)
{
MetaBackgroundPrivate *priv = self->priv;
if (priv->saturation == saturation)
return;
priv->saturation = saturation;
ensure_pipeline (self);
cogl_pipeline_set_uniform_1f (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"saturation"),
priv->saturation);
clutter_content_invalidate (CLUTTER_CONTENT (self));
g_object_notify (G_OBJECT (self), "saturation");
}
static void
add_texture_lookup_shader (MetaBackground *self)
{
MetaBackgroundPrivate *priv = self->priv;
CoglSnippet *snippet;
const char *code = NULL;
if ((priv->effects & META_BACKGROUND_EFFECTS_BLUR) &&
(priv->effects & META_BACKGROUND_EFFECTS_DESATURATE))
code = BLUR_CODE "\n" DESATURATE_CODE;
else if (priv->effects & META_BACKGROUND_EFFECTS_BLUR)
code = BLUR_CODE;
else if (priv->effects & META_BACKGROUND_EFFECTS_DESATURATE)
code = DESATURATE_PRELUDE "\n" DESATURATE_CODE;
else
return;
ensure_pipeline (self);
snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_LOOKUP,
TEXTURE_LOOKUP_SHADER_DECLARATIONS,
NULL);
cogl_snippet_set_replace (snippet, code);
cogl_pipeline_add_layer_snippet (priv->pipeline, 0, snippet);
cogl_object_unref (snippet);
if (priv->effects & META_BACKGROUND_EFFECTS_DESATURATE)
cogl_pipeline_set_uniform_1f (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"saturation"),
priv->saturation);
}
static void
add_vignette (MetaBackground *self)
{
MetaBackgroundPrivate *priv = self->priv;
CoglSnippet *snippet;
ensure_pipeline (self);
snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT, FRAGMENT_SHADER_DECLARATIONS, VIGNETTE_CODE);
cogl_pipeline_add_snippet (priv->pipeline, snippet);
cogl_object_unref (snippet);
cogl_pipeline_set_uniform_1f (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"brightness"),
priv->brightness);
cogl_pipeline_set_uniform_1f (priv->pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline,
"vignette_sharpness"),
priv->vignette_sharpness);
}
static void
set_effects (MetaBackground *self,
MetaBackgroundEffects effects)
{
MetaBackgroundPrivate *priv = self->priv;
priv->effects = effects;
if ((priv->effects & META_BACKGROUND_EFFECTS_BLUR) ||
(priv->effects & META_BACKGROUND_EFFECTS_DESATURATE))
add_texture_lookup_shader (self);
if ((priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE))
add_vignette (self);
clutter_content_invalidate (CLUTTER_CONTENT (self));
}
static void
meta_background_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MetaBackground *self = META_BACKGROUND (object);
MetaBackgroundPrivate *priv = self->priv;
switch (prop_id)
{
case PROP_META_SCREEN:
priv->screen = g_value_get_object (value);
break;
case PROP_MONITOR:
priv->monitor = g_value_get_int (value);
break;
case PROP_EFFECTS:
set_effects (self, g_value_get_flags (value));
break;
case PROP_BRIGHTNESS:
set_brightness (self, g_value_get_float (value));
break;
case PROP_VIGNETTE_SHARPNESS:
set_vignette_sharpness (self, g_value_get_float (value));
break;
case PROP_SATURATION:
set_saturation (self, g_value_get_float (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_background_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MetaBackgroundPrivate *priv = META_BACKGROUND (object)->priv;
switch (prop_id)
{
case PROP_META_SCREEN:
g_value_set_object (value, priv->screen);
break;
case PROP_MONITOR:
g_value_set_int (value, priv->monitor);
break;
case PROP_EFFECTS:
g_value_set_flags (value, priv->effects);
break;
case PROP_BRIGHTNESS:
g_value_set_float (value, priv->brightness);
break;
case PROP_VIGNETTE_SHARPNESS:
g_value_set_float (value, priv->vignette_sharpness);
break;
case PROP_SATURATION:
g_value_set_float (value, priv->saturation);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_background_class_init (MetaBackgroundClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *param_spec;
g_type_class_add_private (klass, sizeof (MetaBackgroundPrivate));
object_class->dispose = meta_background_dispose;
object_class->set_property = meta_background_set_property;
object_class->get_property = meta_background_get_property;
param_spec = g_param_spec_object ("meta-screen",
"MetaScreen",
"MetaScreen",
META_TYPE_SCREEN,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_property (object_class,
PROP_META_SCREEN,
param_spec);
param_spec = g_param_spec_int ("monitor",
"monitor",
"monitor",
0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_property (object_class,
PROP_MONITOR,
param_spec);
param_spec = g_param_spec_float ("brightness",
"brightness",
"Values less than 1.0 dim background",
0.0, 1.0,
1.0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_property (object_class, PROP_BRIGHTNESS, param_spec);
param_spec = g_param_spec_float ("vignette-sharpness",
"vignette-sharpness",
"How obvious the vignette fringe is",
0.0, 1.0,
0.7,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_property (object_class, PROP_VIGNETTE_SHARPNESS, param_spec);
param_spec = g_param_spec_float ("saturation",
"saturation",
"Values less than 1.0 grays background",
0.0, 1.0,
1.0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_property (object_class, PROP_SATURATION, param_spec);
param_spec = g_param_spec_flags ("effects",
"Effects",
"Set to alter saturation, to blur, etc",
meta_background_effects_get_type (),
META_BACKGROUND_EFFECTS_NONE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_EFFECTS, param_spec);
}
static void
meta_background_init (MetaBackground *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
META_TYPE_BACKGROUND,
MetaBackgroundPrivate);
}
static void
unset_texture (MetaBackground *self)
{
MetaBackgroundPrivate *priv = self->priv;
cogl_pipeline_set_layer_texture (priv->pipeline, 0, NULL);
g_clear_pointer (&priv->texture,
(GDestroyNotify)
cogl_object_unref);
}
static void
set_texture (MetaBackground *self,
CoglTexture *texture)
{
MetaBackgroundPrivate *priv = self->priv;
priv->texture = texture;
cogl_pipeline_set_layer_texture (priv->pipeline, 0, priv->texture);
}
static void
set_style (MetaBackground *self,
GDesktopBackgroundStyle style)
{
MetaBackgroundPrivate *priv = self->priv;
CoglPipelineWrapMode wrap_mode;
priv->style = style;
wrap_mode = get_wrap_mode (self);
cogl_pipeline_set_layer_wrap_mode (priv->pipeline, 0, wrap_mode);
}
static void
set_filename (MetaBackground *self,
const char *filename)
{
MetaBackgroundPrivate *priv = self->priv;
g_free (priv->filename);
priv->filename = g_strdup (filename);
}
static Pixmap
get_still_frame_for_monitor (MetaScreen *screen,
int monitor)
{
MetaDisplay *display = meta_screen_get_display (screen);
Display *xdisplay = meta_display_get_xdisplay (display);
Window xroot = meta_screen_get_xroot (screen);
Pixmap pixmap;
GC gc;
XGCValues values;
MetaRectangle geometry;
int depth;
meta_screen_get_monitor_geometry (screen, monitor, &geometry);
depth = DefaultDepth (xdisplay, meta_screen_get_screen_number (screen));
pixmap = XCreatePixmap (xdisplay,
xroot,
geometry.width, geometry.height, depth);
values.function = GXcopy;
values.plane_mask = AllPlanes;
values.fill_style = FillSolid;
values.subwindow_mode = IncludeInferiors;
gc = XCreateGC (xdisplay,
xroot,
GCFunction | GCPlaneMask | GCFillStyle | GCSubwindowMode,
&values);
XCopyArea (xdisplay,
xroot, pixmap, gc,
geometry.x, geometry.y,
geometry.width, geometry.height,
0, 0);
XFreeGC (xdisplay, gc);
return pixmap;
}
/**
* meta_background_load_still_frame:
* @self: the #MetaBackground
*
* Takes a screenshot of the desktop and uses it as the background
* source.
*/
void
meta_background_load_still_frame (MetaBackground *self)
{
MetaBackgroundPrivate *priv = self->priv;
MetaDisplay *display = meta_screen_get_display (priv->screen);
Pixmap still_frame;
CoglTexture *texture;
CoglContext *context = clutter_backend_get_cogl_context (clutter_get_default_backend ());
GError *error = NULL;
ensure_pipeline (self);
unset_texture (self);
set_style (self, G_DESKTOP_BACKGROUND_STYLE_STRETCHED);
still_frame = get_still_frame_for_monitor (priv->screen, priv->monitor);
XSync (meta_display_get_xdisplay (display), False);
meta_error_trap_push (display);
texture = COGL_TEXTURE (cogl_texture_pixmap_x11_new (context, still_frame, FALSE, &error));
meta_error_trap_pop (display);
if (error != NULL)
{
g_warning ("Failed to create background texture from pixmap: %s",
error->message);
g_error_free (error);
return;
}
set_texture (self, texture);
}
/**
* meta_background_load_gradient:
* @self: the #MetaBackground
* @shading_direction: the orientation of the gradient
* @color: the start color of the gradient
* @second_color: the end color of the gradient
*
* Clears any previously set background, and sets the background gradient.
* The gradient starts with @color and
* progresses toward @second_color in the direction of @shading_direction.
*/
void
meta_background_load_gradient (MetaBackground *self,
GDesktopBackgroundShading shading_direction,
ClutterColor *color,
ClutterColor *second_color)
{
MetaBackgroundPrivate *priv = self->priv;
CoglTexture *texture;
guint width, height;
uint8_t pixels[8];
ensure_pipeline (self);
unset_texture (self);
set_style (self, G_DESKTOP_BACKGROUND_STYLE_NONE);
priv->shading_direction = shading_direction;
switch (priv->shading_direction)
{
case G_DESKTOP_BACKGROUND_SHADING_VERTICAL:
width = 1;
height = 2;
break;
case G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL:
width = 2;
height = 1;
break;
default:
g_return_if_reached ();
}
pixels[0] = color->red;
pixels[1] = color->green;
pixels[2] = color->blue;
pixels[3] = color->alpha;
pixels[4] = second_color->red;
pixels[5] = second_color->green;
pixels[6] = second_color->blue;
pixels[7] = second_color->alpha;
texture = cogl_texture_new_from_data (width, height,
COGL_TEXTURE_NO_SLICING,
COGL_PIXEL_FORMAT_RGBA_8888,
COGL_PIXEL_FORMAT_ANY,
4,
pixels);
set_texture (self, COGL_TEXTURE (texture));
}
/**
* meta_background_load_color:
* @self: the #MetaBackground
* @color: a #ClutterColor to solid fill background with
*
* Clears any previously set background, and sets the
* background to a solid color
*
* If @color is %NULL the stage color will be used.
*/
void
meta_background_load_color (MetaBackground *self,
ClutterColor *color)
{
MetaBackgroundPrivate *priv = self->priv;
CoglTexture *texture;
ClutterActor *stage = meta_get_stage_for_screen (priv->screen);
ClutterColor stage_color;
ensure_pipeline (self);
unset_texture (self);
set_style (self, G_DESKTOP_BACKGROUND_STYLE_NONE);
if (color == NULL)
{
clutter_actor_get_background_color (stage, &stage_color);
color = &stage_color;
}
texture = meta_create_color_texture_4ub (color->red,
color->green,
color->blue,
0xff,
COGL_TEXTURE_NO_SLICING);
set_texture (self, COGL_TEXTURE (texture));
}
typedef struct
{
GDesktopBackgroundStyle style;
char *filename;
} LoadFileTaskData;
static LoadFileTaskData *
load_file_task_data_new (const char *filename,
GDesktopBackgroundStyle style)
{
LoadFileTaskData *task_data;
task_data = g_slice_new (LoadFileTaskData);
task_data->style = style;
task_data->filename = g_strdup (filename);
return task_data;
}
static void
load_file_task_data_free (LoadFileTaskData *task_data)
{
g_free (task_data->filename);
g_slice_free (LoadFileTaskData, task_data);
}
static void
load_file (GTask *task,
MetaBackground *self,
LoadFileTaskData *task_data,
GCancellable *cancellable)
{
GError *error = NULL;
GdkPixbuf *pixbuf;
pixbuf = gdk_pixbuf_new_from_file (task_data->filename,
&error);
if (pixbuf == NULL)
{
g_task_return_error (task, error);
return;
}
g_task_return_pointer (task, pixbuf, (GDestroyNotify) g_object_unref);
}
/**
* meta_background_load_file_async:
* @self: the #MetaBackground
* @filename: the image file to load
* @style: a #GDesktopBackgroundStyle to specify how background is laid out
* @cancellable: a #GCancellable
* @callback: call back to call when file is loaded or failed to load
* @user_data: user data for callback
*
* Loads the specified image and uses it as the background source.
*/
void
meta_background_load_file_async (MetaBackground *self,
const char *filename,
GDesktopBackgroundStyle style,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
LoadFileTaskData *task_data;
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
task_data = load_file_task_data_new (filename, style);
g_task_set_task_data (task, task_data, (GDestroyNotify) load_file_task_data_free);
g_task_run_in_thread (task, (GTaskThreadFunc) load_file);
g_object_unref (task);
}
/**
* meta_background_load_file_finish:
* @self: the #MetaBackground
* @result: the result from the #GAsyncReadyCallback passed
* to meta_background_load_file_async()
* @error: a #GError
*
* The finish function for meta_background_load_file_async().
*
* Returns: whether or not the image was loaded
*/
gboolean
meta_background_load_file_finish (MetaBackground *self,
GAsyncResult *result,
GError **error)
{
static CoglUserDataKey key;
GTask *task;
LoadFileTaskData *task_data;
CoglTexture *texture;
GdkPixbuf *pixbuf;
int width, height, row_stride;
guchar *pixels;
gboolean has_alpha;
gboolean loaded = FALSE;
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
task = G_TASK (result);
pixbuf = g_task_propagate_pointer (task, error);
if (pixbuf == NULL)
goto out;
task_data = g_task_get_task_data (task);
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
row_stride = gdk_pixbuf_get_rowstride (pixbuf);
pixels = gdk_pixbuf_get_pixels (pixbuf);
has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
texture = cogl_texture_new_from_data (width,
height,
COGL_TEXTURE_NO_SLICING,
has_alpha ?
COGL_PIXEL_FORMAT_RGBA_8888 :
COGL_PIXEL_FORMAT_RGB_888,
COGL_PIXEL_FORMAT_ANY,
row_stride,
pixels);
if (texture == NULL)
{
g_set_error_literal (error,
COGL_BITMAP_ERROR,
COGL_BITMAP_ERROR_FAILED,
_("background texture could not be created from file"));
goto out;
}
cogl_object_set_user_data (COGL_OBJECT (texture),
&key,
g_object_ref (pixbuf),
(CoglUserDataDestroyCallback)
g_object_unref);
ensure_pipeline (self);
unset_texture (self);
set_style (self, task_data->style);
set_filename (self, task_data->filename);
set_texture (self, texture);
clutter_content_invalidate (CLUTTER_CONTENT (self));
loaded = TRUE;
out:
if (pixbuf != NULL)
g_object_unref (pixbuf);
return loaded;
}
/**
* meta_background_copy:
* @self: a #MetaBackground to copy
* @monitor: a monitor
* @effects: effects to use on copy of @self
*
* Creates a new #MetaBackground to draw the background for the given monitor.
* Background will be loaded from @self and will share state
* with @self, but may have different effects applied to it.
*
* Return value: (transfer full): the newly created background content
*/
MetaBackground *
meta_background_copy (MetaBackground *self,
int monitor,
MetaBackgroundEffects effects)
{
MetaBackground *background;
background = META_BACKGROUND (g_object_new (META_TYPE_BACKGROUND,
"meta-screen", self->priv->screen,
"monitor", monitor,
NULL));
background->priv->brightness = self->priv->brightness;
background->priv->shading_direction = self->priv->shading_direction;
background->priv->color = self->priv->color;
background->priv->second_color = self->priv->second_color;
background->priv->filename = g_strdup (self->priv->filename);
/* we can reuse the pipeline if it has no effects applied, or
* if it has the same effects applied
*/
if (effects == self->priv->effects ||
self->priv->effects == META_BACKGROUND_EFFECTS_NONE)
{
ensure_pipeline (self);
background->priv->pipeline = cogl_pipeline_copy (self->priv->pipeline);
background->priv->texture = cogl_object_ref (self->priv->texture);
background->priv->style = self->priv->style;
background->priv->saturation = self->priv->saturation;
if (effects != self->priv->effects)
{
set_effects (background, effects);
if (effects & META_BACKGROUND_EFFECTS_DESATURATE)
set_saturation (background, self->priv->saturation);
if (effects & META_BACKGROUND_EFFECTS_VIGNETTE)
{
set_brightness (background, self->priv->brightness);
set_vignette_sharpness (background, self->priv->vignette_sharpness);
}
}
else
{
background->priv->effects = self->priv->effects;
}
}
else
{
ensure_pipeline (background);
if (self->priv->texture != NULL)
set_texture (background, cogl_object_ref (self->priv->texture));
set_style (background, self->priv->style);
set_effects (background, effects);
if (effects & META_BACKGROUND_EFFECTS_DESATURATE)
set_saturation (background, self->priv->saturation);
if (effects & META_BACKGROUND_EFFECTS_VIGNETTE)
{
set_brightness (background, self->priv->brightness);
set_vignette_sharpness (background, self->priv->vignette_sharpness);
}
}
clutter_content_invalidate (CLUTTER_CONTENT (background));
return background;
}
/**
* meta_background_new:
* @screen: the #MetaScreen
* @monitor: a monitor in @screen
* @effects: which effect flags to enable
*
* Creates a new #MetaBackground to draw the background for the given monitor.
* The returned object should be set on a #MetaBackgroundActor with
* clutter_actor_set_content().
*
* The background may be desaturated, blurred, or given a vignette depending
* on @effects.
*
* Return value: the newly created background content
*/
MetaBackground *
meta_background_new (MetaScreen *screen,
int monitor,
MetaBackgroundEffects effects)
{
MetaBackground *background;
background = META_BACKGROUND (g_object_new (META_TYPE_BACKGROUND,
"meta-screen", screen,
"monitor", monitor,
"effects", effects,
NULL));
return background;
}
/**
* meta_background_get_style:
* @self: a #MetaBackground
*
* Returns the current background style.
*
* Return value: a #GDesktopBackgroundStyle
*/
GDesktopBackgroundStyle
meta_background_get_style (MetaBackground *self)
{
return self->priv->style;
}
/**
* meta_background_get_shading:
* @self: a #MetaBackground
*
* Returns whether @self is a solid color,
* vertical gradient, horizontal gradient,
* or none of the above.
*
* Return value: a #GDesktopBackgroundShading
*/
GDesktopBackgroundShading
meta_background_get_shading (MetaBackground *self)
{
return self->priv->shading_direction;
}
/**
* meta_background_get_color:
* @self: a #MetaBackground
*
* Returns the first color of @self. If self
* is a gradient, the second color can be returned
* with meta_background_get_second_color().
*
* Return value: (transfer none): a #ClutterColor
*/
const ClutterColor *
meta_background_get_color (MetaBackground *self)
{
return &self->priv->color;
}
/**
* meta_background_get_second_color:
* @self: a #MetaBackground
*
* Returns the second color of @self. If @self
* is not a gradient this function is undefined.
*
* Return value: (transfer none): a #ClutterColor
*/
const ClutterColor *
meta_background_get_second_color (MetaBackground *self)
{
return &self->priv->second_color;
}
/**
* meta_background_get_filename:
* @self: a #MetaBackground
*
* Returns the filename of the currently loaded file.
* IF @self is not loaded from a file this function is
* undefined.
*
* Return value: (transfer none): the filename
*/
const char *
meta_background_get_filename (MetaBackground *self)
{
return self->priv->filename;
}