wayland/egl-compositor.c
Ray Strode 19ad6a9db3 Restore framebuffer when switching back to VT
The kernel currently automatically redirects
output to the kernel framebuffer when switching
VTs away from wayland.  It doesn't restore
output back to wayland's fb when coming back
to the VT.  This patch works around that issue.
2008-12-19 01:49:22 -05:00

1263 lines
32 KiB
C

/*
* Copyright © 2008 Kristian Høgsberg
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting documentation, and
* that the name of the copyright holders not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no representations
* about the suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <i915_drm.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <glib.h>
#include <sys/poll.h>
#include <png.h>
#include <math.h>
#include <linux/input.h>
#include <linux/vt.h>
#include <xf86drmMode.h>
#include <time.h>
#include <fnmatch.h>
#include <dirent.h>
#include <GL/gl.h>
#include <eagle.h>
#include "wayland.h"
#include "cairo-util.h"
#include "egl-compositor.h"
#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
struct wl_visual {
struct wl_object base;
};
struct egl_input_device {
struct wl_object base;
int32_t x, y;
struct egl_compositor *ec;
struct egl_surface *pointer_surface;
struct wl_list link;
int grab;
struct egl_surface *grab_surface;
struct egl_surface *focus_surface;
};
struct egl_compositor {
struct wl_compositor base;
struct wl_visual argb_visual, premultiplied_argb_visual, rgb_visual;
EGLDisplay display;
EGLSurface surface;
EGLContext context;
EGLConfig config;
uint32_t fb_id;
struct wl_display *wl_display;
int gem_fd;
int tty_fd;
int width, height;
struct egl_surface *background;
struct egl_surface *overlay;
double overlay_y, overlay_target, overlay_previous;
struct wl_list input_device_list;
struct wl_list surface_list;
struct wl_event_source *enter_vt_source;
struct wl_event_source *leave_vt_source;
/* Repaint state. */
struct wl_event_source *timer_source;
int repaint_needed;
int repaint_on_timeout;
struct timespec previous_swap;
uint32_t current_frame;
};
struct egl_surface {
struct wl_surface base;
struct egl_compositor *compositor;
struct wl_visual *visual;
GLuint texture;
struct wl_map map;
EGLSurface surface;
int width, height;
struct wl_list link;
};
struct screenshooter {
struct wl_object base;
struct egl_compositor *ec;
};
struct screenshooter_interface {
void (*shoot)(struct wl_client *client, struct screenshooter *shooter);
};
static void
screenshooter_shoot(struct wl_client *client, struct screenshooter *shooter)
{
struct egl_compositor *ec = shooter->ec;
GLuint stride;
static const char filename[] = "wayland-screenshot.png";
GdkPixbuf *pixbuf;
GError *error = NULL;
void *data;
data = eglReadBuffer(ec->display, ec->surface, GL_FRONT_LEFT, &stride);
pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE,
8, ec->width, ec->height, stride,
NULL, NULL);
gdk_pixbuf_save(pixbuf, filename, "png", &error, NULL);
}
static const struct wl_method screenshooter_methods[] = {
{ "shoot", "", NULL }
};
static const struct wl_interface screenshooter_interface = {
"screenshooter", 1,
ARRAY_LENGTH(screenshooter_methods),
screenshooter_methods,
};
struct screenshooter_interface screenshooter_implementation = {
screenshooter_shoot
};
static struct screenshooter *
screenshooter_create(struct egl_compositor *ec)
{
struct screenshooter *shooter;
shooter = malloc(sizeof *shooter);
if (shooter == NULL)
return NULL;
shooter->base.interface = &screenshooter_interface;
shooter->base.implementation = (void(**)(void)) &screenshooter_implementation;
shooter->ec = ec;
return shooter;
};
static struct egl_surface *
egl_surface_create_from_cairo_surface(struct egl_compositor *ec,
cairo_surface_t *surface,
int x, int y, int width, int height)
{
struct egl_surface *es;
int stride;
void *data;
stride = cairo_image_surface_get_stride(surface);
data = cairo_image_surface_get_data(surface);
es = malloc(sizeof *es);
if (es == NULL)
return NULL;
glGenTextures(1, &es->texture);
glBindTexture(GL_TEXTURE_2D, es->texture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_BGRA, GL_UNSIGNED_BYTE, data);
es->compositor = ec;
es->map.x = x;
es->map.y = y;
es->map.width = width;
es->map.height = height;
es->surface = EGL_NO_SURFACE;
es->visual = &ec->premultiplied_argb_visual;
return es;
}
static void
egl_surface_destroy(struct egl_surface *es, struct egl_compositor *ec)
{
glDeleteTextures(1, &es->texture);
if (es->surface != EGL_NO_SURFACE)
eglDestroySurface(ec->display, es->surface);
free(es);
}
static void
pointer_path(cairo_t *cr, int x, int y)
{
const int end = 3, tx = 4, ty = 12, dx = 5, dy = 10;
const int width = 16, height = 16;
cairo_move_to(cr, x, y);
cairo_line_to(cr, x + tx, y + ty);
cairo_line_to(cr, x + dx, y + dy);
cairo_line_to(cr, x + width - end, y + height);
cairo_line_to(cr, x + width, y + height - end);
cairo_line_to(cr, x + dy, y + dx);
cairo_line_to(cr, x + ty, y + tx);
cairo_close_path(cr);
}
static struct egl_surface *
pointer_create(struct egl_compositor *ec, int x, int y, int width, int height)
{
struct egl_surface *es;
const int hotspot_x = 16, hotspot_y = 16;
cairo_surface_t *surface;
cairo_t *cr;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
width, height);
cr = cairo_create(surface);
pointer_path(cr, hotspot_x + 5, hotspot_y + 4);
cairo_set_line_width (cr, 2);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_stroke_preserve(cr);
cairo_fill(cr);
blur_surface(surface, width);
pointer_path(cr, hotspot_x, hotspot_y);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_destroy(cr);
es = egl_surface_create_from_cairo_surface(ec,
surface,
x - hotspot_x,
y - hotspot_y,
width, height);
cairo_surface_destroy(surface);
return es;
}
static struct egl_surface *
background_create(struct egl_compositor *ec,
const char *filename, int width, int height)
{
struct egl_surface *background;
GdkPixbuf *pixbuf;
GError *error = NULL;
void *data;
GLenum format;
background = malloc(sizeof *background);
if (background == NULL)
return NULL;
g_type_init();
pixbuf = gdk_pixbuf_new_from_file_at_scale (filename,
width, height,
FALSE, &error);
if (error != NULL) {
free(background);
return NULL;
}
data = gdk_pixbuf_get_pixels(pixbuf);
glGenTextures(1, &background->texture);
glBindTexture(GL_TEXTURE_2D, background->texture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (gdk_pixbuf_get_has_alpha (pixbuf)) {
format = GL_RGBA;
} else {
format = GL_RGB;
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
format, GL_UNSIGNED_BYTE, data);
background->compositor = ec;
background->map.x = 0;
background->map.y = 0;
background->map.width = width;
background->map.height = height;
background->surface = EGL_NO_SURFACE;
background->visual = &ec->rgb_visual;
return background;
}
static void
rounded_rect(cairo_t *cr, int x0, int y0, int x1, int y1, int radius)
{
cairo_move_to(cr, x0, y0 + radius);
cairo_arc(cr, x0 + radius, y0 + radius, radius, M_PI, 3 * M_PI / 2);
cairo_line_to(cr, x1 - radius, y0);
cairo_arc(cr, x1 - radius, y0 + radius, radius, 3 * M_PI / 2, 2 * M_PI);
cairo_line_to(cr, x1, y1 - radius);
cairo_arc(cr, x1 - radius, y1 - radius, radius, 0, M_PI / 2);
cairo_line_to(cr, x0 + radius, y1);
cairo_arc(cr, x0 + radius, y1 - radius, radius, M_PI / 2, M_PI);
cairo_close_path(cr);
}
static void
draw_button(cairo_t *cr, int x, int y, int width, int height, const char *text)
{
cairo_pattern_t *gradient;
cairo_text_extents_t extents;
double bright = 0.15, dim = 0.02;
int radius = 10;
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_set_line_width (cr, 2);
rounded_rect(cr, x, y, x + width, y + height, radius);
cairo_set_source_rgb(cr, dim, dim, dim);
cairo_stroke(cr);
rounded_rect(cr, x + 2, y + 2, x + width, y + height, radius);
cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
cairo_stroke(cr);
rounded_rect(cr, x + 1, y + 1, x + width - 1, y + height - 1, radius - 1);
cairo_set_source_rgb(cr, bright, bright, bright);
cairo_stroke(cr);
rounded_rect(cr, x + 3, y + 3, x + width - 1, y + height - 1, radius - 1);
cairo_set_source_rgb(cr, dim, dim, dim);
cairo_stroke(cr);
rounded_rect(cr, x + 1, y + 1, x + width - 1, y + height - 1, radius - 1);
gradient = cairo_pattern_create_linear (0, y, 0, y + height);
cairo_pattern_add_color_stop_rgb(gradient, 0, 0.15, 0.15, 0.15);
cairo_pattern_add_color_stop_rgb(gradient, 0.5, 0.08, 0.08, 0.08);
cairo_pattern_add_color_stop_rgb(gradient, 0.5, 0.07, 0.07, 0.07);
cairo_pattern_add_color_stop_rgb(gradient, 1, 0.1, 0.1, 0.1);
cairo_set_source(cr, gradient);
cairo_fill(cr);
cairo_set_font_size(cr, 16);
cairo_text_extents(cr, text, &extents);
cairo_move_to(cr, x + (width - extents.width) / 2, y + (height - extents.height) / 2 - extents.y_bearing);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
cairo_set_line_width (cr, 4);
cairo_text_path(cr, text);
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
}
static struct egl_surface *
overlay_create(struct egl_compositor *ec, int x, int y, int width, int height)
{
struct egl_surface *es;
cairo_surface_t *surface;
cairo_t *cr;
int total_width, button_x, button_y;
const int button_width = 150;
const int button_height = 40;
const int spacing = 50;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
width, height);
cr = cairo_create(surface);
cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 0.8);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
total_width = button_width * 2 + spacing;
button_x = (width - total_width) / 2;
button_y = height - button_height - 20;
draw_button(cr, button_x, button_y, button_width, button_height, "Previous");
button_x += button_width + spacing;
draw_button(cr, button_x, button_y, button_width, button_height, "Next");
cairo_destroy(cr);
es = egl_surface_create_from_cairo_surface(ec, surface,
x, y, width, height);
cairo_surface_destroy(surface);
return es;
}
static void
draw_surface(struct egl_surface *es)
{
struct egl_compositor *ec = es->compositor;
GLint vertices[12];
GLint tex_coords[12] = { 0, 0, 0, 1, 1, 0, 1, 1 };
GLuint indices[4] = { 0, 1, 2, 3 };
vertices[0] = es->map.x;
vertices[1] = es->map.y;
vertices[2] = 0;
vertices[3] = es->map.x;
vertices[4] = es->map.y + es->map.height;
vertices[5] = 0;
vertices[6] = es->map.x + es->map.width;
vertices[7] = es->map.y;
vertices[8] = 0;
vertices[9] = es->map.x + es->map.width;
vertices[10] = es->map.y + es->map.height;
vertices[11] = 0;
if (es->visual == &ec->argb_visual) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
} else if (es->visual == &ec->premultiplied_argb_visual) {
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
glBindTexture(GL_TEXTURE_2D, es->texture);
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_INT, 0, vertices);
glTexCoordPointer(2, GL_INT, 0, tex_coords);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, indices);
}
static void
schedule_repaint(struct egl_compositor *ec);
static void
animate_overlay(struct egl_compositor *ec)
{
double force, y;
int32_t top, bottom;
#if 1
double bounce = 0.0;
double friction = 1.0;
double spring = 0.2;
#else
double bounce = 0.2;
double friction = 0.04;
double spring = 0.09;
#endif
y = ec->overlay_y;
force = (ec->overlay_target - ec->overlay_y) * spring +
(ec->overlay_previous - y) * friction;
ec->overlay_y = y + (y - ec->overlay_previous) + force;
ec->overlay_previous = y;
top = ec->height - ec->overlay->map.height;
bottom = ec->height;
if (ec->overlay_y >= bottom) {
ec->overlay_y = bottom;
ec->overlay_previous = bottom;
}
if (ec->overlay_y <= top) {
ec->overlay_y = top + bounce * (top - ec->overlay_y);
ec->overlay_previous =
top + bounce * (top - ec->overlay_previous);
}
ec->overlay->map.y = ec->overlay_y + 0.5;
if (fabs(y - ec->overlay_target) > 0.2 ||
fabs(ec->overlay_y - ec->overlay_target) > 0.2)
schedule_repaint(ec);
}
static void
repaint(void *data)
{
struct egl_compositor *ec = data;
struct egl_surface *es;
struct egl_input_device *eid;
struct timespec ts;
uint32_t msecs;
if (!ec->repaint_needed) {
ec->repaint_on_timeout = 0;
return;
}
if (ec->background)
draw_surface(ec->background);
else
glClear(GL_COLOR_BUFFER_BIT);
es = container_of(ec->surface_list.next,
struct egl_surface, link);
while (&es->link != &ec->surface_list) {
draw_surface(es);
es = container_of(es->link.next,
struct egl_surface, link);
}
draw_surface(ec->overlay);
eid = container_of(ec->input_device_list.next,
struct egl_input_device, link);
while (&eid->link != &ec->input_device_list) {
draw_surface(eid->pointer_surface);
eid = container_of(eid->link.next,
struct egl_input_device, link);
}
eglSwapBuffers(ec->display, ec->surface);
ec->repaint_needed = 0;
clock_gettime(CLOCK_MONOTONIC, &ts);
msecs = ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000);
wl_display_post_frame(ec->wl_display, &ec->base,
ec->current_frame, msecs);
ec->current_frame++;
wl_event_source_timer_update(ec->timer_source, 10);
ec->repaint_on_timeout = 1;
animate_overlay(ec);
}
static void
schedule_repaint(struct egl_compositor *ec)
{
struct wl_event_loop *loop;
ec->repaint_needed = 1;
if (!ec->repaint_on_timeout) {
loop = wl_display_get_event_loop(ec->wl_display);
wl_event_loop_add_idle(loop, repaint, ec);
}
}
static void
surface_destroy(struct wl_client *client,
struct wl_surface *surface)
{
struct egl_surface *es = (struct egl_surface *) surface;
struct egl_compositor *ec = es->compositor;
wl_list_remove(&es->link);
egl_surface_destroy(es, ec);
schedule_repaint(ec);
}
static void
surface_attach(struct wl_client *client,
struct wl_surface *surface, uint32_t name,
uint32_t width, uint32_t height, uint32_t stride,
struct wl_object *visual)
{
struct egl_surface *es = (struct egl_surface *) surface;
struct egl_compositor *ec = es->compositor;
if (es->surface != EGL_NO_SURFACE)
eglDestroySurface(ec->display, es->surface);
es->width = width;
es->height = height;
es->surface = eglCreateSurfaceForName(ec->display, ec->config,
name, width, height, stride, NULL);
if (visual == &ec->argb_visual.base)
es->visual = &ec->argb_visual;
else if (visual == &ec->premultiplied_argb_visual.base)
es->visual = &ec->premultiplied_argb_visual;
else if (visual == &ec->rgb_visual.base)
es->visual = &ec->rgb_visual;
else
/* FIXME: Smack client with an exception event */;
glBindTexture(GL_TEXTURE_2D, es->texture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
eglBindTexImage(ec->display, es->surface, GL_TEXTURE_2D);
}
static void
surface_map(struct wl_client *client,
struct wl_surface *surface,
int32_t x, int32_t y, int32_t width, int32_t height)
{
struct egl_surface *es = (struct egl_surface *) surface;
es->map.x = x;
es->map.y = y;
es->map.width = width;
es->map.height = height;
}
static void
surface_copy(struct wl_client *client,
struct wl_surface *surface,
int32_t dst_x, int32_t dst_y,
uint32_t name, uint32_t stride,
int32_t x, int32_t y, int32_t width, int32_t height)
{
struct egl_surface *es = (struct egl_surface *) surface;
struct egl_compositor *ec = es->compositor;
EGLSurface src;
/* FIXME: glCopyPixels should work, but then we'll have to
* call eglMakeCurrent to set up the src and dest surfaces
* first. This seems cheaper, but maybe there's a better way
* to accomplish this. */
src = eglCreateSurfaceForName(ec->display, ec->config,
name, x + width, y + height, stride, NULL);
eglCopyNativeBuffers(ec->display, es->surface, GL_FRONT_LEFT, dst_x, dst_y,
src, GL_FRONT_LEFT, x, y, width, height);
eglDestroySurface(ec->display, src);
}
static void
surface_damage(struct wl_client *client,
struct wl_surface *surface,
int32_t x, int32_t y, int32_t width, int32_t height)
{
/* FIXME: This need to take a damage region, of course. */
}
const static struct wl_surface_interface surface_interface = {
surface_destroy,
surface_attach,
surface_map,
surface_copy,
surface_damage
};
static void
compositor_create_surface(struct wl_client *client,
struct wl_compositor *compositor, uint32_t id)
{
struct egl_compositor *ec = (struct egl_compositor *) compositor;
struct egl_surface *es;
es = malloc(sizeof *es);
if (es == NULL)
/* FIXME: Send OOM event. */
return;
es->compositor = ec;
es->surface = EGL_NO_SURFACE;
wl_list_insert(ec->surface_list.prev, &es->link);
glGenTextures(1, &es->texture);
wl_client_add_surface(client, &es->base,
&surface_interface, id);
}
static void
compositor_commit(struct wl_client *client,
struct wl_compositor *compositor, uint32_t key)
{
struct egl_compositor *ec = (struct egl_compositor *) compositor;
schedule_repaint(ec);
wl_client_send_acknowledge(client, compositor, key, ec->current_frame);
}
const static struct wl_compositor_interface compositor_interface = {
compositor_create_surface,
compositor_commit
};
static struct egl_surface *
pick_surface(struct egl_input_device *device)
{
struct egl_compositor *ec = device->ec;
struct egl_surface *es;
if (device->grab > 0)
return device->grab_surface;
es = container_of(ec->surface_list.prev,
struct egl_surface, link);
while (&es->link != &ec->surface_list) {
if (es->map.x <= device->x &&
device->x < es->map.x + es->map.width &&
es->map.y <= device->y &&
device->y < es->map.y + es->map.height)
return es;
es = container_of(es->link.prev,
struct egl_surface, link);
}
return NULL;
}
void
notify_motion(struct egl_input_device *device, int x, int y)
{
struct egl_surface *es;
struct egl_compositor *ec = device->ec;
const int hotspot_x = 16, hotspot_y = 16;
int32_t sx, sy;
if (x < 0)
x = 0;
if (y < 0)
y = 0;
if (x >= ec->width)
x = ec->width - 1;
if (y >= ec->height)
y = ec->height - 1;
es = pick_surface(device);
if (es) {
sx = (x - es->map.x) * es->width / es->map.width;
sy = (y - es->map.y) * es->height / es->map.height;
wl_surface_post_event(&es->base, &device->base,
WL_INPUT_MOTION, x, y, sx, sy);
}
device->x = x;
device->y = y;
device->pointer_surface->map.x = x - hotspot_x;
device->pointer_surface->map.y = y - hotspot_y;
schedule_repaint(device->ec);
}
void
notify_button(struct egl_input_device *device,
int32_t button, int32_t state)
{
struct egl_surface *es;
int32_t sx, sy;
es = pick_surface(device);
if (es) {
wl_list_remove(&es->link);
wl_list_insert(device->ec->surface_list.prev, &es->link);
if (state) {
/* FIXME: We need callbacks when the surfaces
* we reference here go away. */
device->grab++;
device->grab_surface = es;
device->focus_surface = es;
} else {
device->grab--;
}
sx = (device->x - es->map.x) * es->width / es->map.width;
sy = (device->y - es->map.y) * es->height / es->map.height;
/* FIXME: Swallow click on raise? */
wl_surface_post_event(&es->base, &device->base,
WL_INPUT_BUTTON, button, state,
device->x, device->y, sx, sy);
schedule_repaint(device->ec);
}
}
void
notify_key(struct egl_input_device *device,
uint32_t key, uint32_t state)
{
struct egl_compositor *ec = device->ec;
if (key == KEY_ESC && state == 1) {
if (ec->overlay_target == ec->height)
ec->overlay_target -= 200;
else
ec->overlay_target += 200;
schedule_repaint(ec);
} else if (!wl_list_empty(&ec->surface_list)) {
if (device->focus_surface != NULL)
wl_surface_post_event(&device->focus_surface->base,
&device->base,
WL_INPUT_KEY, key, state);
}
}
struct evdev_input_device *
evdev_input_device_create(struct egl_input_device *device,
struct wl_display *display, const char *path);
static void
create_input_device(struct egl_compositor *ec, const char *glob)
{
struct egl_input_device *device;
struct dirent *de;
char path[PATH_MAX];
const char *by_path_dir = "/dev/input/by-path";
DIR *dir;
device = malloc(sizeof *device);
if (device == NULL)
return;
memset(device, 0, sizeof *device);
device->base.interface = wl_input_device_get_interface();
wl_display_add_object(ec->wl_display, &device->base);
device->x = 100;
device->y = 100;
device->pointer_surface = pointer_create(ec,
device->x, device->y, 64, 64);
device->ec = ec;
dir = opendir(by_path_dir);
if (dir == NULL) {
fprintf(stderr, "couldn't read dir %s\n", by_path_dir);
return;
}
while (de = readdir(dir), de != NULL) {
if (fnmatch(glob, de->d_name, 0))
continue;
snprintf(path, sizeof path, "%s/%s", by_path_dir, de->d_name);
evdev_input_device_create(device, ec->wl_display, path);
}
closedir(dir);
wl_list_insert(ec->input_device_list.prev, &device->link);
}
void
egl_device_get_position(struct egl_input_device *device, int32_t *x, int32_t *y)
{
*x = device->x;
*y = device->y;
}
static uint32_t
create_frontbuffer(int fd, int *width, int *height, int *stride, uint32_t *fb_id)
{
drmModeConnector *connector;
drmModeRes *resources;
drmModeEncoder *encoder;
struct drm_mode_modeinfo *mode;
struct drm_i915_gem_create create;
struct drm_gem_flink flink;
int i, ret;
resources = drmModeGetResources(fd);
if (!resources) {
fprintf(stderr, "drmModeGetResources failed\n");
return 0;
}
for (i = 0; i < resources->count_connectors; i++) {
connector = drmModeGetConnector(fd, resources->connectors[i]);
if (connector == NULL)
continue;
if (connector->connection == DRM_MODE_CONNECTED &&
connector->count_modes > 0)
break;
drmModeFreeConnector(connector);
}
if (i == resources->count_connectors) {
fprintf(stderr, "No currently active connector found.\n");
return -1;
}
mode = &connector->modes[0];
for (i = 0; i < resources->count_encoders; i++) {
encoder = drmModeGetEncoder(fd, resources->encoders[i]);
if (encoder == NULL)
continue;
if (encoder->encoder_id == connector->encoder_id)
break;
drmModeFreeEncoder(encoder);
}
/* Mode size at 32 bpp */
create.size = mode->hdisplay * mode->vdisplay * 4;
if (ioctl(fd, DRM_IOCTL_I915_GEM_CREATE, &create) != 0) {
fprintf(stderr, "gem create failed: %m\n");
return 0;
}
ret = drmModeAddFB(fd, mode->hdisplay, mode->vdisplay,
32, 32, mode->hdisplay * 4, create.handle, fb_id);
if (ret) {
fprintf(stderr, "failed to add fb: %m\n");
return 0;
}
ret = drmModeSetCrtc(fd, encoder->crtc_id, *fb_id, 0, 0,
&connector->connector_id, 1, mode);
if (ret) {
fprintf(stderr, "failed to set mode: %m\n");
return 0;
}
flink.handle = create.handle;
if (ioctl(fd, DRM_IOCTL_GEM_FLINK, &flink) != 0) {
fprintf(stderr, "gem flink failed: %m\n");
return 0;
}
*width = mode->hdisplay;
*height = mode->vdisplay;
*stride = mode->hdisplay * 4;
return flink.name;
}
static int
pick_config(struct egl_compositor *ec)
{
EGLConfig configs[100];
EGLint value, count;
int i;
if (!eglGetConfigs(ec->display, configs, ARRAY_LENGTH(configs), &count)) {
fprintf(stderr, "failed to get configs\n");
return -1;
}
ec->config = EGL_NO_CONFIG;
for (i = 0; i < count; i++) {
eglGetConfigAttrib(ec->display,
configs[i],
EGL_DEPTH_SIZE,
&value);
if (value > 0) {
fprintf(stderr, "config %d has depth size %d\n", i, value);
continue;
}
eglGetConfigAttrib(ec->display,
configs[i],
EGL_STENCIL_SIZE,
&value);
if (value > 0) {
fprintf(stderr, "config %d has stencil size %d\n", i, value);
continue;
}
eglGetConfigAttrib(ec->display,
configs[i],
EGL_CONFIG_CAVEAT,
&value);
if (value != EGL_NONE) {
fprintf(stderr, "config %d has caveat %d\n", i, value);
continue;
}
ec->config = configs[i];
break;
}
if (ec->config == EGL_NO_CONFIG) {
fprintf(stderr, "found no config without depth or stencil buffers\n");
return -1;
}
return 0;
}
static const struct wl_interface visual_interface = {
"visual", 1,
};
static void
add_visuals(struct egl_compositor *ec)
{
ec->argb_visual.base.interface = &visual_interface;
ec->argb_visual.base.implementation = NULL;
wl_display_add_object(ec->wl_display, &ec->argb_visual.base);
wl_display_add_global(ec->wl_display, &ec->argb_visual.base);
ec->premultiplied_argb_visual.base.interface = &visual_interface;
ec->premultiplied_argb_visual.base.implementation = NULL;
wl_display_add_object(ec->wl_display,
&ec->premultiplied_argb_visual.base);
wl_display_add_global(ec->wl_display,
&ec->premultiplied_argb_visual.base);
ec->rgb_visual.base.interface = &visual_interface;
ec->rgb_visual.base.implementation = NULL;
wl_display_add_object(ec->wl_display, &ec->rgb_visual.base);
wl_display_add_global(ec->wl_display, &ec->rgb_visual.base);
}
static const char gem_device[] = "/dev/dri/card0";
static const char *macbook_air_default_input_device[] = {
"pci-0000:00:1d.2-usb-0:2:1*event*",
NULL
};
static const char *option_background = "background.jpg";
static const char **option_input_devices = macbook_air_default_input_device;
static const GOptionEntry option_entries[] = {
{ "background", 'b', 0, G_OPTION_ARG_STRING,
&option_background, "Background image" },
{ "input-device", 'i', 0, G_OPTION_ARG_STRING_ARRAY,
&option_input_devices, "Input device glob" },
{ NULL }
};
static void on_enter_vt(int signal_number, void *data)
{
struct egl_compositor *ec = data;
drmModeConnector *connector;
drmModeRes *resources;
drmModeEncoder *encoder;
struct drm_mode_modeinfo *mode;
int i, ret;
int fd;
fd = ec->gem_fd;
resources = drmModeGetResources(fd);
if (!resources) {
fprintf(stderr, "drmModeGetResources failed\n");
return;
}
for (i = 0; i < resources->count_connectors; i++) {
connector = drmModeGetConnector(fd, resources->connectors[i]);
if (connector == NULL)
continue;
if (connector->connection == DRM_MODE_CONNECTED &&
connector->count_modes > 0)
break;
drmModeFreeConnector(connector);
}
if (i == resources->count_connectors) {
fprintf(stderr, "No currently active connector found.\n");
return;
}
mode = &connector->modes[0];
for (i = 0; i < resources->count_encoders; i++) {
encoder = drmModeGetEncoder(fd, resources->encoders[i]);
if (encoder == NULL)
continue;
if (encoder->encoder_id == connector->encoder_id)
break;
drmModeFreeEncoder(encoder);
}
ret = drmModeSetCrtc(fd, encoder->crtc_id, ec->fb_id, 0, 0,
&connector->connector_id, 1, mode);
if (ret) {
fprintf(stderr, "failed to set mode: %m\n");
return;
}
}
static void on_leave_vt(int signal_number, void *data)
{
struct egl_compositor *ec = data;
ioctl (ec->tty_fd, VT_RELDISP, 1);
}
static void watch_for_vt_changes(struct egl_compositor *ec, struct wl_event_loop *loop)
{
int fd;
struct vt_mode mode = { 0 };
ec->tty_fd = open("/dev/tty0", O_RDWR | O_NOCTTY);
mode.mode = VT_PROCESS;
mode.relsig = SIGUSR1;
mode.acqsig = SIGUSR2;
if (!ioctl (fd, VT_SETMODE, &mode) < 0) {
fprintf(stderr, "failed to take control of vt handling\n");
}
ec->leave_vt_source = wl_event_loop_add_signal (loop, SIGUSR1,
on_leave_vt,
ec);
ec->enter_vt_source = wl_event_loop_add_signal (loop, SIGUSR2,
on_enter_vt,
ec);
}
static struct egl_compositor *
egl_compositor_create(struct wl_display *display)
{
EGLint major, minor;
struct egl_compositor *ec;
struct screenshooter *shooter;
uint32_t fb_name;
int i, stride;
struct wl_event_loop *loop;
const static EGLint attribs[] =
{ EGL_RENDER_BUFFER, EGL_BACK_BUFFER, EGL_NONE };
ec = malloc(sizeof *ec);
if (ec == NULL)
return NULL;
ec->wl_display = display;
ec->display = eglCreateDisplayNative(gem_device, "i965");
if (ec->display == NULL) {
fprintf(stderr, "failed to create display\n");
return NULL;
}
if (!eglInitialize(ec->display, &major, &minor)) {
fprintf(stderr, "failed to initialize display\n");
return NULL;
}
if (pick_config(ec))
return NULL;
fb_name = create_frontbuffer(eglGetDisplayFD(ec->display),
&ec->width, &ec->height, &stride, &ec->fb_id);
ec->surface = eglCreateSurfaceForName(ec->display, ec->config,
fb_name, ec->width, ec->height, stride, attribs);
if (ec->surface == NULL) {
fprintf(stderr, "failed to create surface\n");
return NULL;
}
ec->context = eglCreateContext(ec->display, ec->config, NULL, NULL);
if (ec->context == NULL) {
fprintf(stderr, "failed to create context\n");
return NULL;
}
if (!eglMakeCurrent(ec->display, ec->surface, ec->surface, ec->context)) {
fprintf(stderr, "failed to make context current\n");
return NULL;
}
glViewport(0, 0, ec->width, ec->height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, ec->width, ec->height, 0, 0, 1000.0);
glMatrixMode(GL_MODELVIEW);
glClearColor(0, 0, 0.2, 1);
wl_display_set_compositor(display, &ec->base, &compositor_interface);
add_visuals(ec);
wl_list_init(&ec->input_device_list);
for (i = 0; option_input_devices[i]; i++)
create_input_device(ec, option_input_devices[i]);
wl_list_init(&ec->surface_list);
ec->background = background_create(ec, option_background,
ec->width, ec->height);
ec->overlay = overlay_create(ec, 0, ec->height, ec->width, 200);
ec->overlay_y = ec->height;
ec->overlay_target = ec->height;
ec->overlay_previous = ec->height;
ec->gem_fd = open(gem_device, O_RDWR);
if (ec->gem_fd < 0) {
fprintf(stderr, "failed to open drm device\n");
return NULL;
}
shooter = screenshooter_create(ec);
wl_display_add_object(display, &shooter->base);
wl_display_add_global(display, &shooter->base);
loop = wl_display_get_event_loop(ec->wl_display);
watch_for_vt_changes (ec, loop);
ec->timer_source = wl_event_loop_add_timer(loop, repaint, ec);
ec->repaint_needed = 0;
ec->repaint_on_timeout = 0;
schedule_repaint(ec);
return ec;
}
/* The plan here is to generate a random anonymous socket name and
* advertise that through a service on the session dbus.
*/
static const char socket_name[] = "\0wayland";
int main(int argc, char *argv[])
{
struct wl_display *display;
struct egl_compositor *ec;
GError *error = NULL;
GOptionContext *context;
context = g_option_context_new(NULL);
g_option_context_add_main_entries(context, option_entries, "Wayland");
if (!g_option_context_parse(context, &argc, &argv, &error)) {
fprintf(stderr, "option parsing failed: %s\n", error->message);
exit(EXIT_FAILURE);
}
display = wl_display_create();
ec = egl_compositor_create(display);
if (ec == NULL) {
fprintf(stderr, "failed to create compositor\n");
exit(EXIT_FAILURE);
}
if (wl_display_add_socket(display, socket_name, sizeof socket_name)) {
fprintf(stderr, "failed to add socket: %m\n");
exit(EXIT_FAILURE);
}
wl_display_run(display);
return 0;
}