cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

cursor.c (9710B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include "common/blank_cursor.h"
     21#include "common/dot_cursor.h"
     22#include "common/cursor.h"
     23#include "common/ibar_cursor.h"
     24#include "common/pointer_cursor.h"
     25#include "common/surface.h"
     26
     27#include <cairo/cairo.h>
     28#include <guacamole/client.h>
     29#include <guacamole/mem.h>
     30#include <guacamole/protocol.h>
     31#include <guacamole/socket.h>
     32#include <guacamole/timestamp.h>
     33#include <guacamole/user.h>
     34
     35#include <pthread.h>
     36#include <limits.h>
     37#include <stdlib.h>
     38#include <string.h>
     39
     40
     41/**
     42 * Allocates a cursor as well as an image buffer where the cursor is rendered.
     43 *
     44 * @param client
     45 *     The client owning the cursor.
     46 *
     47 * @return
     48 *     The newly-allocated cursor or NULL if cursor cannot be allocated.
     49 */
     50guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
     51
     52    guac_common_cursor* cursor = guac_mem_alloc(sizeof(guac_common_cursor));
     53    if (cursor == NULL)
     54        return NULL;
     55
     56    /* Associate cursor with client and allocate cursor buffer */
     57    cursor->client = client;
     58    cursor->buffer = guac_client_alloc_buffer(client);
     59
     60    /* Allocate initial image buffer */
     61    cursor->image_buffer_size = GUAC_COMMON_CURSOR_DEFAULT_SIZE;
     62    cursor->image_buffer = guac_mem_alloc(cursor->image_buffer_size);
     63
     64    /* No cursor image yet */
     65    cursor->width = 0;
     66    cursor->height = 0;
     67    cursor->surface = NULL;
     68    cursor->hotspot_x = 0;
     69    cursor->hotspot_y = 0;
     70
     71    /* No user has moved the mouse yet */
     72    cursor->user = NULL;
     73    cursor->timestamp = guac_timestamp_current();
     74
     75    /* Start cursor in upper-left */
     76    cursor->x = 0;
     77    cursor->y = 0;
     78
     79    pthread_mutex_init(&(cursor->_lock), NULL);
     80
     81    return cursor;
     82
     83}
     84
     85void guac_common_cursor_free(guac_common_cursor* cursor) {
     86
     87    pthread_mutex_destroy(&(cursor->_lock));
     88
     89    guac_client* client = cursor->client;
     90    guac_layer* buffer = cursor->buffer;
     91    cairo_surface_t* surface = cursor->surface;
     92
     93    /* Free image buffer and surface */
     94    guac_mem_free(cursor->image_buffer);
     95    if (surface != NULL)
     96        cairo_surface_destroy(surface);
     97
     98    /* Destroy buffer within remotely-connected client */
     99    guac_protocol_send_dispose(client->socket, buffer);
    100
    101    /* Return buffer to pool */
    102    guac_client_free_buffer(client, buffer);
    103
    104    guac_mem_free(cursor);
    105
    106}
    107
    108void guac_common_cursor_dup(
    109        guac_common_cursor* cursor, guac_client* client, guac_socket* socket) {
    110
    111    pthread_mutex_lock(&(cursor->_lock));
    112
    113    /* Synchronize location */
    114    guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask,
    115            cursor->timestamp);
    116
    117    /* Synchronize cursor image */
    118    if (cursor->surface != NULL) {
    119        guac_protocol_send_size(socket, cursor->buffer,
    120                cursor->width, cursor->height);
    121
    122        guac_client_stream_png(client, socket, GUAC_COMP_SRC,
    123                cursor->buffer, 0, 0, cursor->surface);
    124
    125        guac_protocol_send_cursor(socket,
    126                cursor->hotspot_x, cursor->hotspot_y,
    127                cursor->buffer, 0, 0, cursor->width, cursor->height);
    128    }
    129
    130    pthread_mutex_unlock(&(cursor->_lock));
    131
    132    guac_socket_flush(socket);
    133
    134}
    135
    136/**
    137 * Callback for guac_client_foreach_user() which sends the current cursor
    138 * position and button state to any given user except the user that moved the
    139 * cursor last.
    140 *
    141 * @param data
    142 *     A pointer to the guac_common_cursor whose state should be broadcast to
    143 *     all users except the user that moved the cursor last.
    144 *
    145 * @return
    146 *     Always NULL.
    147 */
    148static void* guac_common_cursor_broadcast_state(guac_user* user,
    149        void* data) {
    150
    151    guac_common_cursor* cursor = (guac_common_cursor*) data;
    152
    153    /* Send cursor state only if the user is not moving the cursor */
    154    if (user != cursor->user) {
    155        guac_protocol_send_mouse(user->socket, cursor->x, cursor->y,
    156                cursor->button_mask, cursor->timestamp);
    157        guac_socket_flush(user->socket);
    158    }
    159
    160    return NULL;
    161
    162}
    163
    164void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user,
    165        int x, int y, int button_mask) {
    166
    167    pthread_mutex_lock(&(cursor->_lock));
    168
    169    /* Update current user of cursor */
    170    cursor->user = user;
    171
    172    /* Update cursor state */
    173    cursor->x = x;
    174    cursor->y = y;
    175    cursor->button_mask = button_mask;
    176
    177    /* Store time at which cursor was updated */
    178    cursor->timestamp = guac_timestamp_current();
    179
    180    /* Notify all other users of change in cursor state */
    181    guac_client_foreach_user(cursor->client,
    182            guac_common_cursor_broadcast_state, cursor);
    183
    184    pthread_mutex_unlock(&(cursor->_lock));
    185
    186}
    187
    188/**
    189 * Ensures the cursor image buffer has enough room to fit an image with the 
    190 * given characteristics. Existing image buffer data may be destroyed.
    191 *
    192 * @param cursor
    193 *     The cursor whose buffer size should be checked. If this cursor lacks
    194 *     sufficient space to contain a cursor image of the specified width,
    195 *     height, and stride, the current contents of this cursor will be
    196 *     destroyed and replaced with an new buffer having sufficient space.
    197 *
    198 * @param width
    199 *     The required cursor width, in pixels.
    200 *
    201 * @param height
    202 *     The required cursor height, in pixels.
    203 *
    204 * @param stride
    205 *     The number of bytes in each row of image data.
    206 */
    207static void guac_common_cursor_resize(guac_common_cursor* cursor,
    208        int width, int height, int stride) {
    209
    210    size_t minimum_size = guac_mem_ckd_mul_or_die(height, stride);
    211
    212    /* Grow image buffer if necessary */
    213    if (cursor->image_buffer_size < minimum_size) {
    214
    215        /* Calculate new size */
    216        cursor->image_buffer_size = guac_mem_ckd_mul_or_die(minimum_size, 2);
    217
    218        /* Destructively reallocate image buffer */
    219        guac_mem_free(cursor->image_buffer);
    220        cursor->image_buffer = guac_mem_alloc(cursor->image_buffer_size);
    221
    222    }
    223
    224}
    225
    226void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy,
    227    unsigned const char* data, int width, int height, int stride) {
    228
    229    pthread_mutex_lock(&(cursor->_lock));
    230
    231    /* Copy image data */
    232    guac_common_cursor_resize(cursor, width, height, stride);
    233    memcpy(cursor->image_buffer, data, height * stride);
    234
    235    if (cursor->surface != NULL)
    236        cairo_surface_destroy(cursor->surface);
    237
    238    cursor->surface = cairo_image_surface_create_for_data(cursor->image_buffer,
    239            CAIRO_FORMAT_ARGB32, width, height, stride);
    240
    241    /* Set new cursor parameters */
    242    cursor->width = width;
    243    cursor->height = height;
    244    cursor->hotspot_x = hx;
    245    cursor->hotspot_y = hy;
    246
    247    /* Broadcast new cursor image to all users */
    248    guac_protocol_send_size(cursor->client->socket, cursor->buffer,
    249            width, height);
    250
    251    guac_client_stream_png(cursor->client, cursor->client->socket,
    252            GUAC_COMP_SRC, cursor->buffer, 0, 0, cursor->surface);
    253
    254    /* Update cursor image */
    255    guac_protocol_send_cursor(cursor->client->socket,
    256            cursor->hotspot_x, cursor->hotspot_y,
    257            cursor->buffer, 0, 0, cursor->width, cursor->height);
    258
    259    guac_socket_flush(cursor->client->socket);
    260
    261    pthread_mutex_unlock(&(cursor->_lock));
    262
    263}
    264
    265void guac_common_cursor_set_surface(guac_common_cursor* cursor, int hx, int hy,
    266    guac_common_surface* surface) {
    267
    268    /* Set cursor to surface contents */
    269    guac_common_cursor_set_argb(cursor, hx, hy, surface->buffer,
    270            surface->width, surface->height, surface->stride);
    271
    272}
    273
    274void guac_common_cursor_set_pointer(guac_common_cursor* cursor) {
    275
    276    guac_common_cursor_set_argb(cursor, 0, 0,
    277            guac_common_pointer_cursor,
    278            guac_common_pointer_cursor_width,
    279            guac_common_pointer_cursor_height,
    280            guac_common_pointer_cursor_stride);
    281
    282}
    283
    284void guac_common_cursor_set_dot(guac_common_cursor* cursor) {
    285
    286     guac_common_cursor_set_argb(cursor, 2, 2,
    287            guac_common_dot_cursor,
    288            guac_common_dot_cursor_width,
    289            guac_common_dot_cursor_height,
    290            guac_common_dot_cursor_stride);
    291
    292}
    293
    294void guac_common_cursor_set_ibar(guac_common_cursor* cursor) {
    295
    296     guac_common_cursor_set_argb(cursor,
    297            guac_common_ibar_cursor_width / 2,
    298            guac_common_ibar_cursor_height / 2,
    299            guac_common_ibar_cursor,
    300            guac_common_ibar_cursor_width,
    301            guac_common_ibar_cursor_height,
    302            guac_common_ibar_cursor_stride);
    303
    304}
    305
    306void guac_common_cursor_set_blank(guac_common_cursor* cursor) {
    307
    308     guac_common_cursor_set_argb(cursor, 0, 0,
    309            guac_common_blank_cursor,
    310            guac_common_blank_cursor_width,
    311            guac_common_blank_cursor_height,
    312            guac_common_blank_cursor_stride);
    313
    314}
    315
    316void guac_common_cursor_remove_user(guac_common_cursor* cursor,
    317        guac_user* user) {
    318
    319    pthread_mutex_lock(&(cursor->_lock));
    320
    321    /* Disassociate from given user */
    322    if (cursor->user == user)
    323        cursor->user = NULL;
    324
    325    pthread_mutex_unlock(&(cursor->_lock));
    326
    327}
    328