blob: 283f9f34e62a42b1ce24b05ee77c727f1842c527 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-priv.h"
#include "guacamole/assert.h"
#include "guacamole/display.h"
#include "guacamole/rect.h"
#include "guacamole/rwlock.h"
#include <cairo/cairo.h>
#include <stdint.h>
#include <string.h>
/**
* Notifies the display associated with the given layer that the given layer
* has been modified in some way for the current pending frame. If the layer is
* not the cursor layer, the pending_frame_dirty_excluding_mouse flag of the
* display is updated accordingly.
*
* @param layer
* The layer that was modified.
*/
static void PFW_guac_display_layer_touch(guac_display_layer* layer) {
guac_display* display = layer->display;
if (layer != display->cursor_buffer)
display->pending_frame_dirty_excluding_mouse = 1;
}
void guac_display_layer_get_bounds(guac_display_layer* layer, guac_rect* bounds) {
guac_display* display = layer->display;
guac_rwlock_acquire_read_lock(&display->pending_frame.lock);
*bounds = (guac_rect) {
.left = 0,
.top = 0,
.right = layer->pending_frame.width,
.bottom = layer->pending_frame.height
};
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_move(guac_display_layer* layer, int x, int y) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.x = x;
layer->pending_frame.y = y;
PFW_guac_display_layer_touch(layer);
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_stack(guac_display_layer* layer, int z) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.z = z;
PFW_guac_display_layer_touch(layer);
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_set_parent(guac_display_layer* layer, const guac_display_layer* parent) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.parent = parent->layer;
PFW_guac_display_layer_touch(layer);
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_set_opacity(guac_display_layer* layer, int opacity) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.opacity = opacity;
PFW_guac_display_layer_touch(layer);
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_set_lossless(guac_display_layer* layer, int lossless) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.lossless = lossless;
PFW_guac_display_layer_touch(layer);
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_set_multitouch(guac_display_layer* layer, int touches) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.touches = touches;
PFW_guac_display_layer_touch(layer);
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_resize(guac_display_layer* layer, int width, int height) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
PFW_guac_display_layer_resize(layer, width, height);
PFW_guac_display_layer_touch(layer);
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_raw_context_set(guac_display_layer_raw_context* context,
const guac_rect* dst, uint32_t color) {
size_t dst_stride = context->stride;
unsigned char* restrict dst_buffer = GUAC_DISPLAY_LAYER_RAW_BUFFER(context, *dst);
for (int dy = dst->top; dy < dst->bottom; dy++) {
uint32_t* dst_pixel = (uint32_t*) dst_buffer;
dst_buffer += dst_stride;
for (int dx = dst->left; dx < dst->right; dx++)
*(dst_pixel++) = color;
}
guac_rect_extend(&(context->dirty), dst);
}
void guac_display_layer_raw_context_put(guac_display_layer_raw_context* context,
const guac_rect* dst, const void* restrict buffer, size_t stride) {
size_t dst_stride = context->stride;
unsigned char* restrict dst_buffer = GUAC_DISPLAY_LAYER_RAW_BUFFER(context, *dst);
const unsigned char* restrict src_buffer = (const unsigned char*) buffer;
size_t copy_length = guac_mem_ckd_mul_or_die(guac_rect_width(dst),
GUAC_DISPLAY_LAYER_RAW_BPP);
for (int dy = dst->top; dy < dst->bottom; dy++) {
memcpy(dst_buffer, src_buffer, copy_length);
dst_buffer += dst_stride;
src_buffer += stride;
}
guac_rect_extend(&(context->dirty), dst);
}
guac_display_layer_raw_context* guac_display_layer_open_raw(guac_display_layer* layer) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
/* Flush any outstanding Cairo operations before directly accessing buffer */
guac_display_layer_cairo_context* cairo_context = &(layer->pending_frame_cairo_context);
if (cairo_context->surface != NULL)
cairo_surface_flush(cairo_context->surface);
layer->pending_frame_raw_context = (guac_display_layer_raw_context) {
.buffer = layer->pending_frame.buffer,
.stride = layer->pending_frame.buffer_stride,
.dirty = { 0 },
.hint_from = layer,
.bounds = {
.left = 0,
.top = 0,
.right = layer->pending_frame.buffer_width,
.bottom = layer->pending_frame.buffer_height
}
};
return &layer->pending_frame_raw_context;
}
void guac_display_layer_close_raw(guac_display_layer* layer, guac_display_layer_raw_context* context) {
guac_display* display = layer->display;
/* Release any Cairo resources that were created around the external buffer,
* in case the details of the buffer have now changed */
if (context->buffer != layer->pending_frame.buffer) {
guac_display_layer_cairo_context* cairo_context = &(layer->pending_frame_cairo_context);
if (cairo_context->surface != NULL) {
/* NOTE: We do NOT flush any outstanding changes to the Cairo
* surface here, as the underlying buffer may already have been
* freed by the caller, and flushing the surface might write to
* that buffer */
cairo_surface_destroy(cairo_context->surface);
cairo_context->surface = NULL;
cairo_destroy(cairo_context->cairo);
cairo_context->cairo = NULL;
}
}
/* Replace buffer if requested with an external buffer. This intentionally
* falls through to the following buffer_is_external check to update the
* buffer details. */
if (context->buffer != layer->pending_frame.buffer
&& !layer->pending_frame.buffer_is_external) {
guac_mem_free(layer->pending_frame.buffer);
layer->pending_frame.buffer_is_external = 1;
}
/* The details covering the structure of the buffer and the dimensions of
* the layer must be copied from the context if the buffer is external
* (there is no other way to resize a layer with an external buffer) */
if (layer->pending_frame.buffer_is_external) {
int width = guac_rect_width(&context->bounds);
if (width > GUAC_DISPLAY_MAX_WIDTH)
width = GUAC_DISPLAY_MAX_WIDTH;
int height = guac_rect_height(&context->bounds);
if (height > GUAC_DISPLAY_MAX_HEIGHT)
height = GUAC_DISPLAY_MAX_HEIGHT;
layer->pending_frame.buffer = context->buffer;
layer->pending_frame.buffer_width = width;
layer->pending_frame.buffer_height = height;
layer->pending_frame.buffer_stride = context->stride;
/* Update layer size and resize cell tracking array */
PFW_guac_display_layer_resize(layer, width, height);
}
guac_rect_extend(&layer->pending_frame.dirty, &context->dirty);
PFW_guac_display_layer_touch(layer);
/* Apply any hinting regarding scroll/copy optimization */
if (context->hint_from != NULL)
context->hint_from->pending_frame.search_for_copies = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
guac_display_layer_cairo_context* guac_display_layer_open_cairo(guac_display_layer* layer) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
/* It is intentionally allowed that the pending frame buffer can be
* replaced with NULL to ensure that references to external buffers can be
* removed prior to guac_display being freed. If the buffer has been
* manually replaced with NULL, further use of that buffer via Cairo
* contexts is not safe nor allowed. */
GUAC_ASSERT(layer->pending_frame.buffer != NULL);
guac_display_layer_cairo_context* context = &(layer->pending_frame_cairo_context);
context->dirty = (guac_rect) { 0 };
context->hint_from = layer;
context->bounds = (guac_rect) {
.left = 0,
.top = 0,
.right = layer->pending_frame.buffer_width,
.bottom = layer->pending_frame.buffer_height
};
if (context->surface == NULL) {
context->surface = cairo_image_surface_create_for_data(
layer->pending_frame.buffer,
layer->opaque ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32,
layer->pending_frame.buffer_width,
layer->pending_frame.buffer_height,
layer->pending_frame.buffer_stride);
context->cairo = cairo_create(context->surface);
}
return context;
}
void guac_display_layer_close_cairo(guac_display_layer* layer, guac_display_layer_cairo_context* context) {
guac_display* display = layer->display;
guac_rect_extend(&layer->pending_frame.dirty, &context->dirty);
PFW_guac_display_layer_touch(layer);
/* Apply any hinting regarding scroll/copy optimization */
if (context->hint_from != NULL)
context->hint_from->pending_frame.search_for_copies = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}