gtk/gdk/broadway/broadway-output.c
Alexander Larsson 03d6d272f7 broadway: Track surface position correctly
surface->x/y (and various x,y arguments) should be in the parent
coordinates, so treat it as such. We also keep track of the root coords
as these are needed for popup positioning.

Also, drop the isTemp property server side and the weird initial
placement at (100, 100) in the daemon. We now fully control window
placement from the client instead. If this is not we want we should do
a serious design for that but until then lets do the simplest thing.
2020-03-13 15:05:59 +01:00

621 lines
17 KiB
C

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <cairo.h>
#include "broadway-output.h"
//#define DEBUG_NODE_SENDING
//#define DEBUG_NODE_SENDING_REMOVE
/************************************************************************
* 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_uint8 (BroadwayOutput *output, guint8 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
patch_uint32 (BroadwayOutput *output, guint32 v, gsize offset)
{
guint8 *buf;
buf = (guint8 *)output->buf->str + offset;
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_uint8 (output, op);
append_uint32 (output, output->serial++);
}
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)
{
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);
}
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_raise_surface(BroadwayOutput *output, int id)
{
write_header (output, BROADWAY_OP_RAISE_SURFACE);
append_uint16 (output, id);
}
void
broadway_output_lower_surface(BroadwayOutput *output, int id)
{
write_header (output, BROADWAY_OP_LOWER_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_roundtrip (BroadwayOutput *output,
int id,
guint32 tag)
{
write_header (output, BROADWAY_OP_ROUNDTRIP);
append_uint16 (output, id);
append_uint32 (output, tag);
}
void
broadway_output_set_show_keyboard (BroadwayOutput *output,
gboolean show)
{
write_header (output, BROADWAY_OP_SET_SHOW_KEYBOARD);
append_uint16 (output, show);
}
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);
}
static gint append_node_depth = -1;
static void
append_type (BroadwayOutput *output, guint32 type, BroadwayNode *node)
{
#ifdef DEBUG_NODE_SENDING
g_print ("%*s%s(%d/%d)", append_node_depth*2, "", broadway_node_type_names[type], node->id, node->output_id);
if (type == BROADWAY_NODE_TEXTURE)
g_print (" tx=%u", node->data[4]);
g_print ("\n");
#endif
append_uint32 (output, type);
}
static BroadwayNode *
lookup_old_node (GHashTable *old_node_lookup,
guint32 id)
{
if (old_node_lookup)
return g_hash_table_lookup (old_node_lookup, GINT_TO_POINTER (id));
return NULL;
}
/***********************************
* This outputs the tree to the client, while at the same time diffing
* against the old tree. This allows us to avoid sending certain
* parts.
*
* Reusing existing dom nodes are problematic because doing so
* automatically inherits all their children. There are two cases
* where we do this:
*
* If the entire sub tree is identical we emit a KEEP_ALL node which
* just reuses the entire old dom subtree.
*
* If a the node is unchanged (but some descendant may have changed),
* and all parents are also unchanged, then we can just avoid
* changing the dom node at all, and we emit a KEEP_THIS node.
*
***********************************/
static void
append_node (BroadwayOutput *output,
BroadwayNode *node,
GHashTable *old_node_lookup)
{
guint32 i;
BroadwayNode *reused_node;
append_node_depth++;
reused_node = lookup_old_node (old_node_lookup, node->id);
if (reused_node)
{
broadway_node_mark_deep_consumed (reused_node, TRUE);
append_type (output, BROADWAY_NODE_REUSE, node);
append_uint32 (output, node->output_id);
}
else
{
append_type (output, node->type, node);
append_uint32 (output, node->output_id);
for (i = 0; i < node->n_data; i++)
append_uint32 (output, node->data[i]);
for (i = 0; i < node->n_children; i++)
append_node (output,
node->children[i],
old_node_lookup);
}
append_node_depth--;
}
static gboolean
should_reuse_node (BroadwayOutput *output,
BroadwayNode *node,
BroadwayNode *old_node)
{
int i;
guint32 new_texture;
if (old_node->reused)
return FALSE;
if (node->type != old_node->type)
return FALSE;
if (broadway_node_equal (node, old_node))
return TRUE;
switch (node->type) {
case BROADWAY_NODE_TRANSFORM:
#ifdef DEBUG_NODE_SENDING
g_print ("Patching transform node %d/%d\n",
old_node->id, old_node->output_id);
#endif
append_uint32 (output, BROADWAY_NODE_OP_PATCH_TRANSFORM);
append_uint32 (output, old_node->output_id);
for (i = 0; i < node->n_data; i++)
append_uint32 (output, node->data[i]);
return TRUE;
case BROADWAY_NODE_TEXTURE:
/* Check that the size, etc is the same */
for (i = 0; i < 4; i++)
if (node->data[i] != old_node->data[i])
return FALSE;
new_texture = node->data[4];
#ifdef DEBUG_NODE_SENDING
g_print ("Patching texture node %d/%d to tx=%d\n",
old_node->id, old_node->output_id,
new_texture);
#endif
append_uint32 (output, BROADWAY_NODE_OP_PATCH_TEXTURE);
append_uint32 (output, old_node->output_id);
append_uint32 (output, new_texture);
return TRUE;
break;
default:
return FALSE;
}
}
static BroadwayNode *
append_node_ops (BroadwayOutput *output,
BroadwayNode *node,
BroadwayNode *parent,
BroadwayNode *previous_sibling,
BroadwayNode *old_node,
GHashTable *old_node_lookup)
{
BroadwayNode *reused_node;
guint32 i;
/* Maybe can be reused from the last tree. */
reused_node = lookup_old_node (old_node_lookup, node->id);
if (reused_node)
{
g_assert (node == reused_node);
g_assert (reused_node->reused);
g_assert (!reused_node->consumed); /* Should only be once in the tree, and not consumed otherwise */
broadway_node_mark_deep_consumed (reused_node, TRUE);
if (node == old_node)
{
/* The node in the old tree at the current position is the same, so
we need to do nothing, just don't delete it (which we won't since
its marked used) */
}
else
{
/* We can reuse it, bu it comes from a different place or
order, if so we need to move it in place */
#ifdef DEBUG_NODE_SENDING
g_print ("Move old node %d/%d to parent %d/%d after %d/%d\n",
reused_node->id, reused_node->output_id,
parent ? parent->id : 0,
parent ? parent->output_id : 0,
previous_sibling ? previous_sibling->id : 0,
previous_sibling ? previous_sibling->output_id : 0);
#endif
append_uint32 (output, BROADWAY_NODE_OP_MOVE_AFTER_CHILD);
append_uint32 (output, parent ? parent->output_id : 0);
append_uint32 (output, previous_sibling ? previous_sibling->output_id : 0);
append_uint32 (output, reused_node->output_id);
}
return reused_node;
}
/* If the next node in place is shallowly equal (but not necessarily
* deep equal) we reuse it and tweak its children as needed.
* Except we avoid this for reused node as those make more sense to reuse deeply.
*/
if (old_node && should_reuse_node (output, node, old_node))
{
int old_i = 0;
BroadwayNode *last_child = NULL;
old_node->consumed = TRUE; // Don't reuse again
// We rewrite this new node as it now represents the old node in the browser
node->output_id = old_node->output_id;
/* However, we might need to rewrite then children of old_node */
for (i = 0; i < node->n_children; i++)
{
BroadwayNode *child = node->children[i];
/* Find the next (or first) non-consumed old child, if any */
while (old_i < old_node->n_children &&
old_node->children[old_i]->consumed)
old_i++;
last_child =
append_node_ops (output,
child,
node, /* parent */
last_child,
(old_i < old_node->n_children) ? old_node->children[old_i] : NULL,
old_node_lookup);
}
/* Remaining old nodes are either reused elsewhere, or end up marked not consumed so are deleted at the end */
return old_node;
}
/* Fallback to create a new tree */
#ifdef DEBUG_NODE_SENDING
g_print ("Insert nodes in parent %d/%d, after sibling %d/%d\n",
parent ? parent->id : 0,
parent ? parent->output_id : 0,
previous_sibling ? previous_sibling->id : 0,
previous_sibling ? previous_sibling->output_id : 0);
#endif
append_uint32 (output, BROADWAY_NODE_OP_INSERT_NODE);
append_uint32 (output, parent ? parent->output_id : 0);
append_uint32 (output, previous_sibling ? previous_sibling->output_id : 0);
append_node(output, node, old_node_lookup);
return node;
}
/* Remove non-consumed nodes */
static void
append_node_removes (BroadwayOutput *output,
BroadwayNode *node)
{
// TODO: Use an array of nodes instead
if (!node->consumed)
{
#ifdef DEBUG_NODE_SENDING_REMOVE
g_print ("Remove old node non-consumed node %d/%d\n",
node->id, node->output_id);
#endif
append_uint32 (output, BROADWAY_NODE_OP_REMOVE_NODE);
append_uint32 (output, node->output_id);
}
for (int i = 0; i < node->n_children; i++)
append_node_removes (output, node->children[i]);
}
void
broadway_output_surface_set_nodes (BroadwayOutput *output,
int id,
BroadwayNode *root,
BroadwayNode *old_root,
GHashTable *old_node_lookup)
{
gsize size_pos, start, end;
if (old_root)
{
broadway_node_mark_deep_consumed (old_root, FALSE);
broadway_node_mark_deep_reused (old_root, FALSE);
/* This will modify children of old_root if any are shared */
broadway_node_mark_deep_reused (root, TRUE);
}
write_header (output, BROADWAY_OP_SET_NODES);
append_uint16 (output, id);
size_pos = output->buf->len;
append_uint32 (output, 0);
start = output->buf->len;
#ifdef DEBUG_NODE_SENDING
g_print ("====== node ops for surface %d =======\n", id);
#endif
append_node_ops (output, root, NULL, NULL, old_root, old_node_lookup);
if (old_root)
append_node_removes (output, old_root);
end = output->buf->len;
patch_uint32 (output, (end - start) / 4, size_pos);
}
void
broadway_output_upload_texture (BroadwayOutput *output,
guint32 id,
GBytes *texture)
{
gsize len = g_bytes_get_size (texture);
write_header (output, BROADWAY_OP_UPLOAD_TEXTURE);
append_uint32 (output, id);
append_uint32 (output, (guint32)len);
g_string_append_len (output->buf, g_bytes_get_data (texture, NULL), len);
}
void
broadway_output_release_texture (BroadwayOutput *output,
guint32 id)
{
write_header (output, BROADWAY_OP_RELEASE_TEXTURE);
append_uint32 (output, id);
}