311 lines
8.8 KiB
C++
311 lines
8.8 KiB
C++
/*
|
|
Copyright (C) 2009-2015 Red Hat, Inc.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library 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
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <config.h>
|
|
|
|
#include <spice/qxl_dev.h>
|
|
|
|
#include "red-parse-qxl.h"
|
|
#include "display-channel.h"
|
|
#include "tree.h"
|
|
|
|
static const char *draw_type_to_str(uint8_t type)
|
|
{
|
|
switch (type) {
|
|
case QXL_DRAW_FILL:
|
|
return "QXL_DRAW_FILL";
|
|
case QXL_DRAW_OPAQUE:
|
|
return "QXL_DRAW_OPAQUE";
|
|
case QXL_DRAW_COPY:
|
|
return "QXL_DRAW_COPY";
|
|
case QXL_DRAW_TRANSPARENT:
|
|
return "QXL_DRAW_TRANSPARENT";
|
|
case QXL_DRAW_ALPHA_BLEND:
|
|
return "QXL_DRAW_ALPHA_BLEND";
|
|
case QXL_COPY_BITS:
|
|
return "QXL_COPY_BITS";
|
|
case QXL_DRAW_BLEND:
|
|
return "QXL_DRAW_BLEND";
|
|
case QXL_DRAW_BLACKNESS:
|
|
return "QXL_DRAW_BLACKNESS";
|
|
case QXL_DRAW_WHITENESS:
|
|
return "QXL_DRAW_WHITENESS";
|
|
case QXL_DRAW_INVERS:
|
|
return "QXL_DRAW_INVERS";
|
|
case QXL_DRAW_ROP3:
|
|
return "QXL_DRAW_ROP3";
|
|
case QXL_DRAW_COMPOSITE:
|
|
return "QXL_DRAW_COMPOSITE";
|
|
case QXL_DRAW_STROKE:
|
|
return "QXL_DRAW_STROKE";
|
|
case QXL_DRAW_TEXT:
|
|
return "QXL_DRAW_TEXT";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
static void show_red_drawable(RedDrawable *drawable, const char *prefix)
|
|
{
|
|
if (prefix) {
|
|
printf("%s: ", prefix);
|
|
}
|
|
|
|
printf("%s effect %d bbox(%d %d %d %d)",
|
|
draw_type_to_str(drawable->type),
|
|
drawable->effect,
|
|
drawable->bbox.top,
|
|
drawable->bbox.left,
|
|
drawable->bbox.bottom,
|
|
drawable->bbox.right);
|
|
|
|
switch (drawable->type) {
|
|
case QXL_DRAW_FILL:
|
|
case QXL_DRAW_OPAQUE:
|
|
case QXL_DRAW_COPY:
|
|
case QXL_DRAW_TRANSPARENT:
|
|
case QXL_DRAW_ALPHA_BLEND:
|
|
case QXL_COPY_BITS:
|
|
case QXL_DRAW_BLEND:
|
|
case QXL_DRAW_BLACKNESS:
|
|
case QXL_DRAW_WHITENESS:
|
|
case QXL_DRAW_INVERS:
|
|
case QXL_DRAW_ROP3:
|
|
case QXL_DRAW_COMPOSITE:
|
|
case QXL_DRAW_STROKE:
|
|
case QXL_DRAW_TEXT:
|
|
break;
|
|
default:
|
|
spice_error("bad drawable type");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static void show_draw_item(DrawItem *draw_item, const char *prefix)
|
|
{
|
|
if (prefix) {
|
|
printf("%s: ", prefix);
|
|
}
|
|
printf("effect %d bbox(%d %d %d %d)\n",
|
|
draw_item->effect,
|
|
draw_item->base.rgn.extents.x1,
|
|
draw_item->base.rgn.extents.y1,
|
|
draw_item->base.rgn.extents.x2,
|
|
draw_item->base.rgn.extents.y2);
|
|
}
|
|
|
|
struct DumpItem {
|
|
int level;
|
|
Container *container;
|
|
};
|
|
|
|
static void dump_item(TreeItem *item, void *data)
|
|
{
|
|
auto di = static_cast<DumpItem *>(data);
|
|
const char *item_prefix = "|--";
|
|
int i;
|
|
|
|
if (di->container) {
|
|
while (di->container != item->container) {
|
|
di->level--;
|
|
di->container = di->container->base.container;
|
|
}
|
|
}
|
|
|
|
switch (item->type) {
|
|
case TREE_ITEM_TYPE_DRAWABLE: {
|
|
Drawable *drawable = SPICE_CONTAINEROF(item, Drawable, tree_item.base);
|
|
const int max_indent = 200;
|
|
char indent_str[max_indent + 1];
|
|
int indent_str_len;
|
|
|
|
for (i = 0; i < di->level; i++) {
|
|
printf(" ");
|
|
}
|
|
printf(item_prefix, 0);
|
|
show_red_drawable(drawable->red_drawable.get(), nullptr);
|
|
for (i = 0; i < di->level; i++) {
|
|
printf(" ");
|
|
}
|
|
printf("| ");
|
|
show_draw_item(&drawable->tree_item, nullptr);
|
|
indent_str_len = MIN(max_indent, strlen(item_prefix) + di->level * 2);
|
|
memset(indent_str, ' ', indent_str_len);
|
|
indent_str[indent_str_len] = 0;
|
|
region_dump(&item->rgn, indent_str);
|
|
printf("\n");
|
|
break;
|
|
}
|
|
case TREE_ITEM_TYPE_CONTAINER:
|
|
di->level++;
|
|
di->container = CONTAINER(item);
|
|
break;
|
|
case TREE_ITEM_TYPE_SHADOW:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void tree_foreach(TreeItem *item, void (*f)(TreeItem *, void *), void * data)
|
|
{
|
|
if (!item)
|
|
return;
|
|
|
|
f(item, data);
|
|
|
|
if (item->type == TREE_ITEM_TYPE_CONTAINER) {
|
|
Container *container = CONTAINER(item);
|
|
RingItem *it;
|
|
|
|
RING_FOREACH(it, &container->items) {
|
|
tree_foreach(SPICE_CONTAINEROF(it, TreeItem, siblings_link), f, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tree_item_dump(TreeItem *item)
|
|
{
|
|
DumpItem di = { 0, };
|
|
|
|
spice_return_if_fail(item != nullptr);
|
|
tree_foreach(item, dump_item, &di);
|
|
}
|
|
|
|
/* Creates a new Shadow item for the given DrawItem, with an offset of @delta.
|
|
* A shadow represents a source region for a COPY_BITS operation, while the
|
|
* DrawItem represents the destination region for the operation */
|
|
Shadow* shadow_new(DrawItem *item, const SpicePoint *delta)
|
|
{
|
|
spice_return_val_if_fail(item->shadow == nullptr, NULL);
|
|
if (!delta->x && !delta->y) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto shadow = g_new(Shadow, 1);
|
|
|
|
shadow->base.type = TREE_ITEM_TYPE_SHADOW;
|
|
shadow->base.container = nullptr;
|
|
region_clone(&shadow->base.rgn, &item->base.rgn);
|
|
region_offset(&shadow->base.rgn, delta->x, delta->y);
|
|
ring_item_init(&shadow->base.siblings_link);
|
|
region_init(&shadow->on_hold);
|
|
item->shadow = shadow;
|
|
|
|
return shadow;
|
|
}
|
|
|
|
/* Create a new container to hold @item and insert @item into this container.
|
|
*
|
|
* NOTE: This function assumes that @item is already inside a different Ring,
|
|
* so it removes @item from that ring before inserting it into the new
|
|
* container */
|
|
Container* container_new(DrawItem *item)
|
|
{
|
|
auto container = g_new(Container, 1);
|
|
|
|
container->base.type = TREE_ITEM_TYPE_CONTAINER;
|
|
container->base.container = item->base.container;
|
|
item->base.container = container;
|
|
item->container_root = TRUE;
|
|
region_clone(&container->base.rgn, &item->base.rgn);
|
|
ring_item_init(&container->base.siblings_link);
|
|
ring_add_after(&container->base.siblings_link, &item->base.siblings_link);
|
|
ring_remove(&item->base.siblings_link);
|
|
ring_init(&container->items);
|
|
ring_add(&container->items, &item->base.siblings_link);
|
|
|
|
return container;
|
|
}
|
|
|
|
void container_free(Container *container)
|
|
{
|
|
spice_return_if_fail(ring_is_empty(&container->items));
|
|
|
|
ring_remove(&container->base.siblings_link);
|
|
region_destroy(&container->base.rgn);
|
|
g_free(container);
|
|
}
|
|
|
|
void container_cleanup(Container *container)
|
|
{
|
|
/* visit upward, removing containers */
|
|
/* non-empty container get its element moving up ?? */
|
|
while (container && container->items.next == container->items.prev) {
|
|
Container *next = container->base.container;
|
|
if (container->items.next != &container->items) {
|
|
SPICE_VERIFY(SPICE_OFFSETOF(TreeItem, siblings_link) == 0);
|
|
auto item = reinterpret_cast<TreeItem *>(ring_get_head(&container->items));
|
|
spice_assert(item);
|
|
ring_remove(&item->siblings_link);
|
|
ring_add_after(&item->siblings_link, &container->base.siblings_link);
|
|
item->container = container->base.container;
|
|
}
|
|
container_free(container);
|
|
container = next;
|
|
}
|
|
}
|
|
|
|
/* FIXME: document weird function: go down containers, and return drawable->shadow? */
|
|
Shadow* tree_item_find_shadow(TreeItem *item)
|
|
{
|
|
while (item->type == TREE_ITEM_TYPE_CONTAINER) {
|
|
SPICE_VERIFY(SPICE_OFFSETOF(TreeItem, siblings_link) == 0);
|
|
if (!(item = reinterpret_cast<TreeItem *>(ring_get_tail(&CONTAINER(item)->items)))) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (item->type != TREE_ITEM_TYPE_DRAWABLE) {
|
|
return nullptr;
|
|
}
|
|
|
|
return DRAW_ITEM(item)->shadow;
|
|
}
|
|
|
|
/* return the Ring containing siblings of item, falling back to @ring if @item
|
|
* does not have a container */
|
|
Ring *tree_item_container_items(TreeItem *item, Ring *ring)
|
|
{
|
|
return (item->container) ? &item->container->items : ring;
|
|
}
|
|
|
|
bool tree_item_contained_by(TreeItem *item, Ring *ring)
|
|
{
|
|
spice_assert(item && ring);
|
|
do {
|
|
Ring *now = tree_item_container_items(item, ring);
|
|
if (now == ring) {
|
|
return TRUE;
|
|
}
|
|
} while ((item = &item->container->base)); /* move up one level */
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void draw_item_remove_shadow(DrawItem *item)
|
|
{
|
|
Shadow *shadow;
|
|
|
|
if (!item->shadow) {
|
|
return;
|
|
}
|
|
shadow = item->shadow;
|
|
item->shadow = nullptr;
|
|
ring_remove(&shadow->base.siblings_link);
|
|
region_destroy(&shadow->base.rgn);
|
|
region_destroy(&shadow->on_hold);
|
|
g_free(shadow);
|
|
}
|