forked from AuroraMiddleware/gtk
628 lines
13 KiB
C
628 lines
13 KiB
C
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <cairo.h>
|
|
|
|
#include "broadway-output.h"
|
|
|
|
static cairo_status_t
|
|
write_png_data (void *closure,
|
|
const unsigned char *data,
|
|
unsigned int data_len)
|
|
{
|
|
GString *buf = closure;
|
|
|
|
g_string_append_len (buf, (char *)data, data_len);
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
to_png_rgb (GString *buf, int w, int h, int byte_stride, guint32 *data)
|
|
{
|
|
cairo_surface_t *surface;
|
|
|
|
surface = cairo_image_surface_create_for_data ((guchar *)data,
|
|
CAIRO_FORMAT_RGB24, w, h, byte_stride);
|
|
|
|
cairo_surface_write_to_png_stream (surface, write_png_data, buf);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
static void
|
|
to_png_rgba (GString *buf, int w, int h, int byte_stride, guint32 *data)
|
|
{
|
|
cairo_surface_t *surface;
|
|
|
|
surface = cairo_image_surface_create_for_data ((guchar *)data,
|
|
CAIRO_FORMAT_ARGB32, w, h, byte_stride);
|
|
|
|
cairo_surface_write_to_png_stream (surface, write_png_data, buf);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
/************************************************************************
|
|
* Basic I/O primitives *
|
|
************************************************************************/
|
|
|
|
struct BroadwayOutput {
|
|
GOutputStream *out;
|
|
GString *buf;
|
|
int error;
|
|
guint32 serial;
|
|
};
|
|
|
|
static void
|
|
broadway_output_send_cmd (BroadwayOutput *output,
|
|
gboolean fin, BroadwayWSOpCode code,
|
|
const void *buf, gsize count)
|
|
{
|
|
gboolean mask = FALSE;
|
|
guchar header[16];
|
|
size_t p;
|
|
|
|
gboolean mid_header = count > 125 && count <= 65535;
|
|
gboolean long_header = count > 65535;
|
|
|
|
/* NB. big-endian spec => bit 0 == MSB */
|
|
header[0] = ( (fin ? 0x80 : 0) | (code & 0x0f) );
|
|
header[1] = ( (mask ? 0x80 : 0) |
|
|
(mid_header ? 126 : long_header ? 127 : count) );
|
|
p = 2;
|
|
if (mid_header)
|
|
{
|
|
*(guint16 *)(header + p) = GUINT16_TO_BE( (guint16)count );
|
|
p += 2;
|
|
}
|
|
else if (long_header)
|
|
{
|
|
*(guint64 *)(header + p) = GUINT64_TO_BE( count );
|
|
p += 8;
|
|
}
|
|
// FIXME: if we are paranoid we should 'mask' the data
|
|
// FIXME: we should really emit these as a single write
|
|
g_output_stream_write_all (output->out, header, p, NULL, NULL, NULL);
|
|
g_output_stream_write_all (output->out, buf, count, NULL, NULL, NULL);
|
|
}
|
|
|
|
void broadway_output_pong (BroadwayOutput *output)
|
|
{
|
|
broadway_output_send_cmd (output, TRUE, BROADWAY_WS_CNX_PONG, NULL, 0);
|
|
}
|
|
|
|
int
|
|
broadway_output_flush (BroadwayOutput *output)
|
|
{
|
|
if (output->buf->len == 0)
|
|
return TRUE;
|
|
|
|
broadway_output_send_cmd (output, TRUE, BROADWAY_WS_BINARY,
|
|
output->buf->str, output->buf->len);
|
|
|
|
g_string_set_size (output->buf, 0);
|
|
|
|
return !output->error;
|
|
|
|
}
|
|
|
|
BroadwayOutput *
|
|
broadway_output_new (GOutputStream *out, guint32 serial)
|
|
{
|
|
BroadwayOutput *output;
|
|
|
|
output = g_new0 (BroadwayOutput, 1);
|
|
|
|
output->out = g_object_ref (out);
|
|
output->buf = g_string_new ("");
|
|
output->serial = serial;
|
|
|
|
return output;
|
|
}
|
|
|
|
void
|
|
broadway_output_free (BroadwayOutput *output)
|
|
{
|
|
g_object_unref (output->out);
|
|
free (output);
|
|
}
|
|
|
|
guint32
|
|
broadway_output_get_next_serial (BroadwayOutput *output)
|
|
{
|
|
return output->serial;
|
|
}
|
|
|
|
void
|
|
broadway_output_set_next_serial (BroadwayOutput *output,
|
|
guint32 serial)
|
|
{
|
|
output->serial = serial;
|
|
}
|
|
|
|
|
|
/************************************************************************
|
|
* Core rendering operations *
|
|
************************************************************************/
|
|
|
|
static void
|
|
append_char (BroadwayOutput *output, char c)
|
|
{
|
|
g_string_append_c (output->buf, c);
|
|
}
|
|
|
|
static void
|
|
append_bool (BroadwayOutput *output, gboolean val)
|
|
{
|
|
g_string_append_c (output->buf, val ? 1: 0);
|
|
}
|
|
|
|
static void
|
|
append_flags (BroadwayOutput *output, guint32 val)
|
|
{
|
|
g_string_append_c (output->buf, val);
|
|
}
|
|
|
|
static void
|
|
append_uint16 (BroadwayOutput *output, guint32 v)
|
|
{
|
|
gsize old_len = output->buf->len;
|
|
guint8 *buf;
|
|
|
|
g_string_set_size (output->buf, old_len + 2);
|
|
buf = (guint8 *)output->buf->str + old_len;
|
|
buf[0] = (v >> 0) & 0xff;
|
|
buf[1] = (v >> 8) & 0xff;
|
|
}
|
|
|
|
static void
|
|
append_uint32 (BroadwayOutput *output, guint32 v)
|
|
{
|
|
gsize old_len = output->buf->len;
|
|
guint8 *buf;
|
|
|
|
g_string_set_size (output->buf, old_len + 4);
|
|
buf = (guint8 *)output->buf->str + old_len;
|
|
buf[0] = (v >> 0) & 0xff;
|
|
buf[1] = (v >> 8) & 0xff;
|
|
buf[2] = (v >> 16) & 0xff;
|
|
buf[3] = (v >> 24) & 0xff;
|
|
}
|
|
|
|
static void
|
|
overwrite_uint32 (BroadwayOutput *output, gsize pos, guint32 v)
|
|
{
|
|
guint8 *buf = (guint8 *)output->buf->str + pos;
|
|
|
|
buf[0] = (v >> 0) & 0xff;
|
|
buf[1] = (v >> 8) & 0xff;
|
|
buf[2] = (v >> 16) & 0xff;
|
|
buf[3] = (v >> 24) & 0xff;
|
|
}
|
|
|
|
|
|
static void
|
|
write_header(BroadwayOutput *output, char op)
|
|
{
|
|
append_char (output, op);
|
|
append_uint32 (output, output->serial++);
|
|
}
|
|
|
|
void
|
|
broadway_output_copy_rectangles (BroadwayOutput *output, int id,
|
|
BroadwayRect *rects, int n_rects,
|
|
int dx, int dy)
|
|
{
|
|
int i;
|
|
|
|
write_header (output, BROADWAY_OP_COPY_RECTANGLES);
|
|
append_uint16 (output, id);
|
|
append_uint16 (output, n_rects);
|
|
for (i = 0; i < n_rects; i++)
|
|
{
|
|
append_uint16 (output, rects[i].x);
|
|
append_uint16 (output, rects[i].y);
|
|
append_uint16 (output, rects[i].width);
|
|
append_uint16 (output, rects[i].height);
|
|
}
|
|
append_uint16 (output, dx);
|
|
append_uint16 (output, dy);
|
|
}
|
|
|
|
void
|
|
broadway_output_grab_pointer (BroadwayOutput *output,
|
|
int id,
|
|
gboolean owner_event)
|
|
{
|
|
write_header (output, BROADWAY_OP_GRAB_POINTER);
|
|
append_uint16 (output, id);
|
|
append_bool (output, owner_event);
|
|
}
|
|
|
|
guint32
|
|
broadway_output_ungrab_pointer (BroadwayOutput *output)
|
|
{
|
|
guint32 serial;
|
|
|
|
serial = output->serial;
|
|
write_header (output, BROADWAY_OP_UNGRAB_POINTER);
|
|
|
|
return serial;
|
|
}
|
|
|
|
void
|
|
broadway_output_new_surface(BroadwayOutput *output,
|
|
int id, int x, int y, int w, int h,
|
|
gboolean is_temp)
|
|
{
|
|
write_header (output, BROADWAY_OP_NEW_SURFACE);
|
|
append_uint16 (output, id);
|
|
append_uint16 (output, x);
|
|
append_uint16 (output, y);
|
|
append_uint16 (output, w);
|
|
append_uint16 (output, h);
|
|
append_bool (output, is_temp);
|
|
}
|
|
|
|
void
|
|
broadway_output_disconnected (BroadwayOutput *output)
|
|
{
|
|
write_header (output, BROADWAY_OP_DISCONNECTED);
|
|
}
|
|
|
|
void
|
|
broadway_output_show_surface(BroadwayOutput *output, int id)
|
|
{
|
|
write_header (output, BROADWAY_OP_SHOW_SURFACE);
|
|
append_uint16 (output, id);
|
|
}
|
|
|
|
void
|
|
broadway_output_hide_surface(BroadwayOutput *output, int id)
|
|
{
|
|
write_header (output, BROADWAY_OP_HIDE_SURFACE);
|
|
append_uint16 (output, id);
|
|
}
|
|
|
|
void
|
|
broadway_output_destroy_surface(BroadwayOutput *output, int id)
|
|
{
|
|
write_header (output, BROADWAY_OP_DESTROY_SURFACE);
|
|
append_uint16 (output, id);
|
|
}
|
|
|
|
|
|
void
|
|
broadway_output_move_resize_surface (BroadwayOutput *output,
|
|
int id,
|
|
gboolean has_pos,
|
|
int x,
|
|
int y,
|
|
gboolean has_size,
|
|
int w,
|
|
int h)
|
|
{
|
|
int val;
|
|
|
|
if (!has_pos && !has_size)
|
|
return;
|
|
|
|
write_header (output, BROADWAY_OP_MOVE_RESIZE);
|
|
val = (!!has_pos) | ((!!has_size) << 1);
|
|
append_uint16 (output, id);
|
|
append_flags (output, val);
|
|
if (has_pos)
|
|
{
|
|
append_uint16 (output, x);
|
|
append_uint16 (output, y);
|
|
}
|
|
if (has_size)
|
|
{
|
|
append_uint16 (output, w);
|
|
append_uint16 (output, h);
|
|
}
|
|
}
|
|
|
|
void
|
|
broadway_output_set_transient_for (BroadwayOutput *output,
|
|
int id,
|
|
int parent_id)
|
|
{
|
|
write_header (output, BROADWAY_OP_SET_TRANSIENT_FOR);
|
|
append_uint16 (output, id);
|
|
append_uint16 (output, parent_id);
|
|
}
|
|
|
|
|
|
void
|
|
broadway_output_put_rgb (BroadwayOutput *output, int id, int x, int y,
|
|
int w, int h, int byte_stride, void *data)
|
|
{
|
|
gsize size_start, image_start, len;
|
|
|
|
write_header (output, BROADWAY_OP_PUT_RGB);
|
|
|
|
append_uint16 (output, id);
|
|
append_uint16 (output, x);
|
|
append_uint16 (output, y);
|
|
|
|
size_start = output->buf->len;
|
|
append_uint32 (output, 0);
|
|
|
|
image_start = output->buf->len;
|
|
to_png_rgb (output->buf, w, h, byte_stride, (guint32*)data);
|
|
|
|
len = output->buf->len - image_start;
|
|
|
|
overwrite_uint32 (output, size_start, len);
|
|
}
|
|
|
|
typedef struct {
|
|
int x1, y1;
|
|
int x2, y2;
|
|
} BroadwayBox;
|
|
|
|
static int
|
|
is_any_x_set (unsigned char *data,
|
|
int box_x1, int box_x2,
|
|
int x1, int x2, int y, int *x_set,
|
|
int byte_stride)
|
|
{
|
|
int w ;
|
|
guint32 *ptr;
|
|
|
|
if (x1 < box_x1)
|
|
x1 = box_x1;
|
|
|
|
if (x2 > box_x2)
|
|
x2 = box_x2;
|
|
|
|
w = x2 - x1;
|
|
if (w > 0)
|
|
{
|
|
ptr = (guint32 *)(data + y * byte_stride + x1 * 4);
|
|
while (w-- > 0)
|
|
{
|
|
if (*ptr != 0)
|
|
{
|
|
if (x_set)
|
|
*x_set = x1;
|
|
return 1;
|
|
}
|
|
ptr++;
|
|
x1++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define EXTEND_X_FUZZ 10
|
|
#define EXTEND_Y_FUZZ 10
|
|
|
|
static int
|
|
extend_x_range (unsigned char *data,
|
|
int box_x1, int box_y1,
|
|
int box_x2, int box_y2,
|
|
int *x1, int *x2, int y,
|
|
int byte_stride)
|
|
{
|
|
int extended = 0;
|
|
int new_x;
|
|
|
|
while (is_any_x_set (data, box_x1, box_x2, *x1 - EXTEND_X_FUZZ, *x1, y, &new_x, byte_stride))
|
|
{
|
|
*x1 = new_x;
|
|
extended = 1;
|
|
}
|
|
|
|
while (is_any_x_set (data, box_x1, box_x2, *x2, *x2 + EXTEND_X_FUZZ, y, &new_x, byte_stride))
|
|
{
|
|
*x2 = new_x + 1;
|
|
extended = 1;
|
|
}
|
|
|
|
return extended;
|
|
}
|
|
|
|
static int
|
|
extend_y_range (unsigned char *data,
|
|
int box_x1, int box_y1,
|
|
int box_x2, int box_y2,
|
|
int x1, int x2, int *y,
|
|
int byte_stride)
|
|
{
|
|
int extended = 0;
|
|
int found_set;
|
|
int yy, y2;
|
|
|
|
while (*y < box_y2)
|
|
{
|
|
found_set = 0;
|
|
|
|
y2 = *y + EXTEND_Y_FUZZ;
|
|
if (y2 > box_y2)
|
|
y2 = box_y2;
|
|
|
|
for (yy = y2; yy > *y + 1; yy--)
|
|
{
|
|
if (is_any_x_set (data, box_x1, box_x2, x1, x2, yy - 1, NULL, byte_stride))
|
|
{
|
|
found_set = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!found_set)
|
|
break;
|
|
*y = yy;
|
|
extended = 1;
|
|
}
|
|
|
|
return extended;
|
|
}
|
|
|
|
|
|
static void
|
|
rgba_find_rects_extents (unsigned char *data,
|
|
int box_x1, int box_y1,
|
|
int box_x2, int box_y2,
|
|
int x, int y,
|
|
BroadwayBox *rect,
|
|
int byte_stride)
|
|
{
|
|
int x1, x2, y1, y2, yy;
|
|
int extended;
|
|
|
|
x1 = x;
|
|
x2 = x + 1;
|
|
y1 = y;
|
|
y2 = y + 1;
|
|
|
|
do
|
|
{
|
|
/* Expand maximally for all known rows */
|
|
do
|
|
{
|
|
extended = 0;
|
|
|
|
for (yy = y1; yy < y2; yy++)
|
|
extended |= extend_x_range (data,
|
|
box_x1, box_y1,
|
|
box_x2, box_y2,
|
|
&x1, &x2, yy,
|
|
byte_stride);
|
|
}
|
|
while (extended);
|
|
}
|
|
while (extend_y_range(data,
|
|
box_x1, box_y1,
|
|
box_x2, box_y2,
|
|
x1, x2, &y2,
|
|
byte_stride));
|
|
|
|
rect->x1 = x1;
|
|
rect->x2 = x2;
|
|
rect->y1 = y1;
|
|
rect->y2 = y2;
|
|
}
|
|
|
|
static void
|
|
rgba_find_rects_sub (unsigned char *data,
|
|
int box_x1, int box_y1,
|
|
int box_x2, int box_y2,
|
|
int byte_stride,
|
|
BroadwayBox **rects,
|
|
int *n_rects, int *alloc_rects)
|
|
{
|
|
guint32 *line;
|
|
BroadwayBox rect;
|
|
int x, y;
|
|
|
|
if (box_x1 == box_x2 || box_y1 == box_y2)
|
|
return;
|
|
|
|
for (y = box_y1; y < box_y2; y++)
|
|
{
|
|
line = (guint32 *)(data + y * byte_stride + box_x1 * 4);
|
|
|
|
for (x = box_x1; x < box_x2; x++)
|
|
{
|
|
if (*line != 0)
|
|
{
|
|
rgba_find_rects_extents (data,
|
|
box_x1, box_y1, box_x2, box_y2,
|
|
x, y, &rect, byte_stride);
|
|
if (*n_rects == *alloc_rects)
|
|
{
|
|
(*alloc_rects) *= 2;
|
|
*rects = g_renew (BroadwayBox, *rects, *alloc_rects);
|
|
}
|
|
(*rects)[*n_rects] = rect;
|
|
(*n_rects)++;
|
|
rgba_find_rects_sub (data,
|
|
box_x1, rect.y1,
|
|
rect.x1, rect.y2,
|
|
byte_stride,
|
|
rects, n_rects, alloc_rects);
|
|
rgba_find_rects_sub (data,
|
|
rect.x2, rect.y1,
|
|
box_x2, rect.y2,
|
|
byte_stride,
|
|
rects, n_rects, alloc_rects);
|
|
rgba_find_rects_sub (data,
|
|
box_x1, rect.y2,
|
|
box_x2, box_y2,
|
|
byte_stride,
|
|
rects, n_rects, alloc_rects);
|
|
return;
|
|
}
|
|
line++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static BroadwayBox *
|
|
rgba_find_rects (unsigned char *data,
|
|
int w, int h, int byte_stride,
|
|
int *n_rects)
|
|
{
|
|
BroadwayBox *rects;
|
|
int alloc_rects;
|
|
|
|
alloc_rects = 20;
|
|
rects = g_new (BroadwayBox, alloc_rects);
|
|
|
|
*n_rects = 0;
|
|
rgba_find_rects_sub (data,
|
|
0, 0, w, h, byte_stride,
|
|
&rects, n_rects, &alloc_rects);
|
|
|
|
return rects;
|
|
}
|
|
|
|
void
|
|
broadway_output_put_rgba (BroadwayOutput *output, int id, int x, int y,
|
|
int w, int h, int byte_stride, void *data)
|
|
{
|
|
BroadwayBox *rects;
|
|
int i, n_rects;
|
|
gsize size_start, image_start, len;
|
|
|
|
rects = rgba_find_rects (data, w, h, byte_stride, &n_rects);
|
|
|
|
for (i = 0; i < n_rects; i++)
|
|
{
|
|
guint8 *subdata;
|
|
|
|
write_header (output, BROADWAY_OP_PUT_RGB);
|
|
append_uint16 (output, id);
|
|
append_uint16 (output, x + rects[i].x1);
|
|
append_uint16 (output, y + rects[i].y1);
|
|
|
|
size_start = output->buf->len;
|
|
append_uint32 (output, 0);
|
|
|
|
image_start = output->buf->len;
|
|
|
|
subdata = (guint8 *)data + rects[i].x1 * 4 + rects[i].y1 * byte_stride;
|
|
to_png_rgba (output->buf, rects[i].x2 - rects[i].x1,
|
|
rects[i].y2 - rects[i].y1,
|
|
byte_stride, (guint32*)subdata);
|
|
|
|
len = output->buf->len - image_start;
|
|
|
|
overwrite_uint32 (output, size_start, len);
|
|
}
|
|
|
|
free (rects);
|
|
}
|
|
|
|
void
|
|
broadway_output_surface_flush (BroadwayOutput *output,
|
|
int id)
|
|
{
|
|
write_header (output, BROADWAY_OP_FLUSH);
|
|
append_uint16 (output, id);
|
|
}
|