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