GUACAMOLE-313: Merge add support for logging mouse cursor information.
This commit is contained in:
commit
08f854ffef
@ -102,6 +102,27 @@ typedef struct guac_common_cursor {
|
||||
*/
|
||||
int y;
|
||||
|
||||
/**
|
||||
* An integer value representing the current state of each button, where
|
||||
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
|
||||
* button is currently pressed. The lowest-order bit is the left mouse
|
||||
* button, followed by the middle button, right button, and finally the up
|
||||
* and down buttons of the scroll wheel.
|
||||
*
|
||||
* @see GUAC_CLIENT_MOUSE_LEFT
|
||||
* @see GUAC_CLIENT_MOUSE_MIDDLE
|
||||
* @see GUAC_CLIENT_MOUSE_RIGHT
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
|
||||
*/
|
||||
int button_mask;
|
||||
|
||||
/**
|
||||
* The server timestamp representing the point in time when the mousr
|
||||
* location was last updated.
|
||||
*/
|
||||
guac_timestamp timestamp;
|
||||
|
||||
} guac_common_cursor;
|
||||
|
||||
/**
|
||||
@ -142,12 +163,12 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
|
||||
guac_socket* socket);
|
||||
|
||||
/**
|
||||
* Moves the mouse cursor, marking the given user as the most recent user of
|
||||
* the mouse. The remote mouse cursor will be hidden for this user and shown
|
||||
* for all others.
|
||||
* Updates the current position and button state of the mouse cursor, marking
|
||||
* the given user as the most recent user of the mouse. The remote mouse cursor
|
||||
* will be hidden for this user and shown for all others.
|
||||
*
|
||||
* @param cursor
|
||||
* The cursor being moved.
|
||||
* The cursor being updated.
|
||||
*
|
||||
* @param user
|
||||
* The user that moved the cursor.
|
||||
@ -157,9 +178,22 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
|
||||
*
|
||||
* @param y
|
||||
* The new Y coordinate of the cursor.
|
||||
*
|
||||
* @param button_mask
|
||||
* An integer value representing the current state of each button, where
|
||||
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
|
||||
* button is currently pressed. The lowest-order bit is the left mouse
|
||||
* button, followed by the middle button, right button, and finally the up
|
||||
* and down buttons of the scroll wheel.
|
||||
*
|
||||
* @see GUAC_CLIENT_MOUSE_LEFT
|
||||
* @see GUAC_CLIENT_MOUSE_MIDDLE
|
||||
* @see GUAC_CLIENT_MOUSE_RIGHT
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
|
||||
*/
|
||||
void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user,
|
||||
int x, int y);
|
||||
void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user,
|
||||
int x, int y, int button_mask);
|
||||
|
||||
/**
|
||||
* Sets the cursor image to the given raw image data. This raw image data must
|
||||
|
@ -42,6 +42,21 @@
|
||||
*/
|
||||
#define GUAC_COMMON_RECORDING_MAX_NAME_LENGTH 2048
|
||||
|
||||
/**
|
||||
* An in-progress session recording, attached to a guac_client instance such
|
||||
* that output Guacamole instructions may be dynamically intercepted and
|
||||
* written to a file.
|
||||
*/
|
||||
typedef struct guac_common_recording {
|
||||
|
||||
/**
|
||||
* The guac_socket which writes directly to the recording file, rather than
|
||||
* to any particular user.
|
||||
*/
|
||||
guac_socket* socket;
|
||||
|
||||
} guac_common_recording;
|
||||
|
||||
/**
|
||||
* Replaces the socket of the given client such that all further Guacamole
|
||||
* protocol output will be copied into a file within the given path and having
|
||||
@ -68,11 +83,51 @@
|
||||
* exist.
|
||||
*
|
||||
* @return
|
||||
* Zero if the recording file has been successfully created and a recording
|
||||
* will be written, non-zero otherwise.
|
||||
* A new guac_common_recording structure representing the in-progress
|
||||
* recording if the recording file has been successfully created and a
|
||||
* recording will be written, NULL otherwise.
|
||||
*/
|
||||
int guac_common_recording_create(guac_client* client, const char* path,
|
||||
const char* name, int create_path);
|
||||
guac_common_recording* guac_common_recording_create(guac_client* client,
|
||||
const char* path, const char* name, int create_path);
|
||||
|
||||
/**
|
||||
* Frees the resources associated with the given in-progress recording. Note
|
||||
* that, due to the manner that recordings are attached to the guac_client, the
|
||||
* underlying guac_socket is not freed. The guac_socket will be automatically
|
||||
* freed when the guac_client is freed.
|
||||
*
|
||||
* @param recording
|
||||
* The guac_common_recording to free.
|
||||
*/
|
||||
void guac_common_recording_free(guac_common_recording* recording);
|
||||
|
||||
/**
|
||||
* Reports the current mouse position and button state within the recording.
|
||||
*
|
||||
* @param recording
|
||||
* The guac_common_recording associated with the mouse that has moved.
|
||||
*
|
||||
* @param x
|
||||
* The new X coordinate of the mouse cursor, in pixels.
|
||||
*
|
||||
* @param y
|
||||
* The new Y coordinate of the mouse cursor, in pixels.
|
||||
*
|
||||
* @param button_mask
|
||||
* An integer value representing the current state of each button, where
|
||||
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
|
||||
* button is currently pressed. The lowest-order bit is the left mouse
|
||||
* button, followed by the middle button, right button, and finally the up
|
||||
* and down buttons of the scroll wheel.
|
||||
*
|
||||
* @see GUAC_CLIENT_MOUSE_LEFT
|
||||
* @see GUAC_CLIENT_MOUSE_MIDDLE
|
||||
* @see GUAC_CLIENT_MOUSE_RIGHT
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
|
||||
*/
|
||||
void guac_common_recording_report_mouse(guac_common_recording* recording,
|
||||
int x, int y, int button_mask);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
#include <limits.h>
|
||||
@ -67,6 +68,7 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
|
||||
|
||||
/* No user has moved the mouse yet */
|
||||
cursor->user = NULL;
|
||||
cursor->timestamp = guac_timestamp_current();
|
||||
|
||||
/* Start cursor in upper-left */
|
||||
cursor->x = 0;
|
||||
@ -101,7 +103,8 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
|
||||
guac_socket* socket) {
|
||||
|
||||
/* Synchronize location */
|
||||
guac_protocol_send_mouse(socket, cursor->x, cursor->y);
|
||||
guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask,
|
||||
cursor->timestamp);
|
||||
|
||||
/* Synchronize cursor image */
|
||||
if (cursor->surface != NULL) {
|
||||
@ -122,23 +125,25 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
|
||||
|
||||
/**
|
||||
* Callback for guac_client_foreach_user() which sends the current cursor
|
||||
* position to any given user except the user that moved the cursor last.
|
||||
* position and button state to any given user except the user that moved the
|
||||
* cursor last.
|
||||
*
|
||||
* @param data
|
||||
* A pointer to the guac_common_cursor whose position should be broadcast
|
||||
* to all users except the user that moved the cursor last.
|
||||
* A pointer to the guac_common_cursor whose state should be broadcast to
|
||||
* all users except the user that moved the cursor last.
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
static void* guac_common_cursor_broadcast_position(guac_user* user,
|
||||
static void* guac_common_cursor_broadcast_state(guac_user* user,
|
||||
void* data) {
|
||||
|
||||
guac_common_cursor* cursor = (guac_common_cursor*) data;
|
||||
|
||||
/* Send cursor position only if the user is not moving the cursor */
|
||||
/* Send cursor state only if the user is not moving the cursor */
|
||||
if (user != cursor->user) {
|
||||
guac_protocol_send_mouse(user->socket, cursor->x, cursor->y);
|
||||
guac_protocol_send_mouse(user->socket, cursor->x, cursor->y,
|
||||
cursor->button_mask, cursor->timestamp);
|
||||
guac_socket_flush(user->socket);
|
||||
}
|
||||
|
||||
@ -146,19 +151,23 @@ static void* guac_common_cursor_broadcast_position(guac_user* user,
|
||||
|
||||
}
|
||||
|
||||
void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user,
|
||||
int x, int y) {
|
||||
void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user,
|
||||
int x, int y, int button_mask) {
|
||||
|
||||
/* Update current user of cursor */
|
||||
cursor->user = user;
|
||||
|
||||
/* Update cursor position */
|
||||
/* Update cursor state */
|
||||
cursor->x = x;
|
||||
cursor->y = y;
|
||||
cursor->button_mask = button_mask;
|
||||
|
||||
/* Notify all other users of change in cursor position */
|
||||
/* Store time at which cursor was updated */
|
||||
cursor->timestamp = guac_timestamp_current();
|
||||
|
||||
/* Notify all other users of change in cursor state */
|
||||
guac_client_foreach_user(cursor->client,
|
||||
guac_common_cursor_broadcast_position, cursor);
|
||||
guac_common_cursor_broadcast_state, cursor);
|
||||
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,9 @@
|
||||
#include "common/recording.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <direct.h>
|
||||
@ -133,8 +135,8 @@ static int guac_common_recording_open(const char* path,
|
||||
|
||||
}
|
||||
|
||||
int guac_common_recording_create(guac_client* client, const char* path,
|
||||
const char* name, int create_path) {
|
||||
guac_common_recording* guac_common_recording_create(guac_client* client,
|
||||
const char* path, const char* name, int create_path) {
|
||||
|
||||
char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH];
|
||||
|
||||
@ -146,7 +148,7 @@ int guac_common_recording_create(guac_client* client, const char* path,
|
||||
#endif
|
||||
guac_client_log(client, GUAC_LOG_ERROR,
|
||||
"Creation of recording failed: %s", strerror(errno));
|
||||
return 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Attempt to open recording file */
|
||||
@ -154,18 +156,35 @@ int guac_common_recording_create(guac_client* client, const char* path,
|
||||
if (fd == -1) {
|
||||
guac_client_log(client, GUAC_LOG_ERROR,
|
||||
"Creation of recording failed: %s", strerror(errno));
|
||||
return 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Replace client socket with wrapped socket */
|
||||
client->socket = guac_socket_tee(client->socket, guac_socket_open(fd));
|
||||
/* Create recording structure with reference to underlying socket */
|
||||
guac_common_recording* recording = malloc(sizeof(guac_common_recording));
|
||||
recording->socket = guac_socket_open(fd);
|
||||
|
||||
/* Replace client socket with wrapped recording socket */
|
||||
client->socket = guac_socket_tee(client->socket, recording->socket);
|
||||
|
||||
/* Recording creation succeeded */
|
||||
guac_client_log(client, GUAC_LOG_INFO,
|
||||
"Recording of session will be saved to \"%s\".",
|
||||
filename);
|
||||
|
||||
return 0;
|
||||
return recording;
|
||||
|
||||
}
|
||||
|
||||
void guac_common_recording_free(guac_common_recording* recording) {
|
||||
free(recording);
|
||||
}
|
||||
|
||||
void guac_common_recording_report_mouse(guac_common_recording* recording,
|
||||
int x, int y, int button_mask) {
|
||||
|
||||
/* Report mouse location */
|
||||
guac_protocol_send_mouse(recording->socket, x, y, button_mask,
|
||||
guac_timestamp_current());
|
||||
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ man_MANS = \
|
||||
|
||||
noinst_HEADERS = \
|
||||
buffer.h \
|
||||
cursor.h \
|
||||
display.h \
|
||||
encode.h \
|
||||
ffmpeg-compat.h \
|
||||
@ -41,6 +42,7 @@ noinst_HEADERS = \
|
||||
|
||||
guacenc_SOURCES = \
|
||||
buffer.c \
|
||||
cursor.c \
|
||||
display.c \
|
||||
display-buffers.c \
|
||||
display-image-streams.c \
|
||||
@ -59,6 +61,7 @@ guacenc_SOURCES = \
|
||||
instruction-dispose.c \
|
||||
instruction-end.c \
|
||||
instruction-img.c \
|
||||
instruction-mouse.c \
|
||||
instruction-move.c \
|
||||
instruction-rect.c \
|
||||
instruction-shade.c \
|
||||
|
59
src/guacenc/cursor.c
Normal file
59
src/guacenc/cursor.c
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 "config.h"
|
||||
#include "buffer.h"
|
||||
#include "cursor.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
guacenc_cursor* guacenc_cursor_alloc() {
|
||||
|
||||
/* Allocate new cursor */
|
||||
guacenc_cursor* cursor = (guacenc_cursor*) malloc(sizeof(guacenc_cursor));
|
||||
if (cursor == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Allocate associated buffer (image) */
|
||||
cursor->buffer = guacenc_buffer_alloc();
|
||||
if (cursor->buffer == NULL) {
|
||||
free(cursor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Do not initially render cursor, unless it moves */
|
||||
cursor->x = cursor->y = -1;
|
||||
|
||||
return cursor;
|
||||
|
||||
}
|
||||
|
||||
void guacenc_cursor_free(guacenc_cursor* cursor) {
|
||||
|
||||
/* Ignore NULL cursors */
|
||||
if (cursor == NULL)
|
||||
return;
|
||||
|
||||
/* Free underlying buffer */
|
||||
guacenc_buffer_free(cursor->buffer);
|
||||
|
||||
free(cursor);
|
||||
|
||||
}
|
||||
|
87
src/guacenc/cursor.h
Normal file
87
src/guacenc/cursor.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef GUACENC_CURSOR_H
|
||||
#define GUACENC_CURSOR_H
|
||||
|
||||
#include "config.h"
|
||||
#include "buffer.h"
|
||||
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
/**
|
||||
* A mouse cursor, having a current location, hostspot, and associated cursor
|
||||
* image.
|
||||
*/
|
||||
typedef struct guacenc_cursor {
|
||||
|
||||
/**
|
||||
* The current X coordinate of the mouse cursor, in pixels. Valid values
|
||||
* are non-negative. Negative values indicate that the cursor should not
|
||||
* be rendered.
|
||||
*/
|
||||
int x;
|
||||
|
||||
/**
|
||||
* The current Y coordinate of the mouse cursor, in pixels. Valid values
|
||||
* are non-negative. Negative values indicate that the cursor should not
|
||||
* be rendered.
|
||||
*/
|
||||
int y;
|
||||
|
||||
/**
|
||||
* The X coordinate of the mouse cursor hotspot within the cursor image,
|
||||
* in pixels.
|
||||
*/
|
||||
int hotspot_x;
|
||||
|
||||
/**
|
||||
* The Y coordinate of the mouse cursor hotspot within the cursor image,
|
||||
* in pixels.
|
||||
*/
|
||||
int hotspot_y;
|
||||
|
||||
/**
|
||||
* The current mouse cursor image.
|
||||
*/
|
||||
guacenc_buffer* buffer;
|
||||
|
||||
} guacenc_cursor;
|
||||
|
||||
/**
|
||||
* Allocates and initializes a new cursor object.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated and initialized guacenc_cursor, or NULL if allocation
|
||||
* fails.
|
||||
*/
|
||||
guacenc_cursor* guacenc_cursor_alloc();
|
||||
|
||||
/**
|
||||
* Frees all memory associated with the given cursor object. If the cursor
|
||||
* provided is NULL, this function has no effect.
|
||||
*
|
||||
* @param cursor
|
||||
* The cursor to free, which may be NULL.
|
||||
*/
|
||||
void guacenc_cursor_free(guacenc_cursor* cursor);
|
||||
|
||||
#endif
|
||||
|
@ -20,9 +20,12 @@
|
||||
#include "config.h"
|
||||
#include "display.h"
|
||||
#include "layer.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@ -75,6 +78,50 @@ static int guacenc_display_layer_comparator(const void* a, const void* b) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the mouse cursor on top of the frame buffer of the default layer of
|
||||
* the given display.
|
||||
*
|
||||
* @param display
|
||||
* The display whose mouse cursor should be rendered to the frame buffer
|
||||
* of its default layer.
|
||||
*
|
||||
* @return
|
||||
* Zero if rendering succeeds, non-zero otherwise.
|
||||
*/
|
||||
static int guacenc_display_render_cursor(guacenc_display* display) {
|
||||
|
||||
guacenc_cursor* cursor = display->cursor;
|
||||
|
||||
/* Do not render cursor if coordinates are negative */
|
||||
if (cursor->x < 0 || cursor->y < 0)
|
||||
return 0;
|
||||
|
||||
/* Retrieve default layer (guaranteed to not be NULL) */
|
||||
guacenc_layer* def_layer = guacenc_display_get_layer(display, 0);
|
||||
assert(def_layer != NULL);
|
||||
|
||||
/* Get source and destination buffers */
|
||||
guacenc_buffer* src = cursor->buffer;
|
||||
guacenc_buffer* dst = def_layer->frame;
|
||||
|
||||
/* Render cursor to layer */
|
||||
if (src->width > 0 && src->height > 0) {
|
||||
cairo_set_source_surface(dst->cairo, src->surface,
|
||||
cursor->x - cursor->hotspot_x,
|
||||
cursor->y - cursor->hotspot_y);
|
||||
cairo_rectangle(dst->cairo,
|
||||
cursor->x - cursor->hotspot_x,
|
||||
cursor->y - cursor->hotspot_y,
|
||||
src->width, src->height);
|
||||
cairo_fill(dst->cairo);
|
||||
}
|
||||
|
||||
/* Always succeeds */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guacenc_display_flatten(guacenc_display* display) {
|
||||
|
||||
int i;
|
||||
@ -151,7 +198,8 @@ int guacenc_display_flatten(guacenc_display* display) {
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
/* Render cursor on top of everything else */
|
||||
return guacenc_display_render_cursor(display);
|
||||
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "cursor.h"
|
||||
#include "display.h"
|
||||
#include "video.h"
|
||||
|
||||
@ -97,6 +98,9 @@ guacenc_display* guacenc_display_alloc(const char* path, const char* codec,
|
||||
/* Associate display with video output */
|
||||
display->output = video;
|
||||
|
||||
/* Allocate special-purpose cursor layer */
|
||||
display->cursor = guacenc_cursor_alloc();
|
||||
|
||||
return display;
|
||||
|
||||
}
|
||||
@ -124,6 +128,9 @@ int guacenc_display_free(guacenc_display* display) {
|
||||
for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++)
|
||||
guacenc_image_stream_free(display->image_streams[i]);
|
||||
|
||||
/* Free cursor */
|
||||
guacenc_cursor_free(display->cursor);
|
||||
|
||||
free(display);
|
||||
return retval;
|
||||
|
||||
|
@ -22,10 +22,12 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "buffer.h"
|
||||
#include "cursor.h"
|
||||
#include "image-stream.h"
|
||||
#include "layer.h"
|
||||
#include "video.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
@ -52,6 +54,11 @@
|
||||
*/
|
||||
typedef struct guacenc_display {
|
||||
|
||||
/**
|
||||
* The current mouse cursor state.
|
||||
*/
|
||||
guacenc_cursor* cursor;
|
||||
|
||||
/**
|
||||
* All currently-allocated buffers. The index of the buffer corresponds to
|
||||
* its position within this array, where -1 is the 0th entry. If a buffer
|
||||
|
@ -18,6 +18,8 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "buffer.h"
|
||||
#include "cursor.h"
|
||||
#include "display.h"
|
||||
#include "log.h"
|
||||
|
||||
@ -36,16 +38,32 @@ int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) {
|
||||
/* Parse arguments */
|
||||
int hotspot_x = atoi(argv[0]);
|
||||
int hotspot_y = atoi(argv[1]);
|
||||
int src_index = atoi(argv[2]);
|
||||
int src_x = atoi(argv[3]);
|
||||
int src_y = atoi(argv[4]);
|
||||
int src_w = atoi(argv[5]);
|
||||
int src_h = atoi(argv[6]);
|
||||
int sindex = atoi(argv[2]);
|
||||
int sx = atoi(argv[3]);
|
||||
int sy = atoi(argv[4]);
|
||||
int width = atoi(argv[5]);
|
||||
int height = atoi(argv[6]);
|
||||
|
||||
/* Nothing to do with cursor (yet) */
|
||||
guacenc_log(GUAC_LOG_DEBUG, "Ignoring cursor: hotspot (%i, %i) "
|
||||
"src_layer=%i (%i, %i) %ix%i", hotspot_x, hotspot_y,
|
||||
src_index, src_x, src_y, src_w, src_h);
|
||||
/* Pull buffer of source layer/buffer */
|
||||
guacenc_buffer* src = guacenc_display_get_related_buffer(display, sindex);
|
||||
if (src == NULL)
|
||||
return 1;
|
||||
|
||||
/* Update cursor hotspot */
|
||||
guacenc_cursor* cursor = display->cursor;
|
||||
cursor->hotspot_x = hotspot_x;
|
||||
cursor->hotspot_y = hotspot_y;
|
||||
|
||||
/* Resize cursor to exactly fit */
|
||||
guacenc_buffer_resize(cursor->buffer, width, height);
|
||||
|
||||
/* Copy rectangle from source to cursor */
|
||||
guacenc_buffer* dst = cursor->buffer;
|
||||
if (src->surface != NULL && dst->cairo != NULL) {
|
||||
cairo_set_operator(dst->cairo, CAIRO_OPERATOR_SOURCE);
|
||||
cairo_set_source_surface(dst->cairo, src->surface, sx, sy);
|
||||
cairo_paint(dst->cairo);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
|
56
src/guacenc/instruction-mouse.c
Normal file
56
src/guacenc/instruction-mouse.c
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 "config.h"
|
||||
#include "cursor.h"
|
||||
#include "display.h"
|
||||
#include "log.h"
|
||||
#include "parse.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
int guacenc_handle_mouse(guacenc_display* display, int argc, char** argv) {
|
||||
|
||||
/* Verify argument count */
|
||||
if (argc < 2) {
|
||||
guacenc_log(GUAC_LOG_WARNING, "\"mouse\" instruction incomplete");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Parse arguments */
|
||||
int x = atoi(argv[0]);
|
||||
int y = atoi(argv[1]);
|
||||
|
||||
/* Update cursor properties */
|
||||
guacenc_cursor* cursor = display->cursor;
|
||||
cursor->x = x;
|
||||
cursor->y = y;
|
||||
|
||||
/* If no timestamp provided, nothing further to do */
|
||||
if (argc < 4)
|
||||
return 0;
|
||||
|
||||
/* Leverage timestamp to render frame */
|
||||
guac_timestamp timestamp = guacenc_parse_timestamp(argv[3]);
|
||||
return guacenc_display_sync(display, timestamp);
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "display.h"
|
||||
#include "log.h"
|
||||
#include "parse.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
@ -27,40 +28,6 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Parses a guac_timestamp from the given string. The string is assumed to
|
||||
* consist solely of decimal digits with an optional leading minus sign. If the
|
||||
* given string contains other characters, the behavior of this function is
|
||||
* undefined.
|
||||
*
|
||||
* @param str
|
||||
* The string to parse, which must contain only decimal digits and an
|
||||
* optional leading minus sign.
|
||||
*
|
||||
* @return
|
||||
* A guac_timestamp having the same value as the provided string.
|
||||
*/
|
||||
static guac_timestamp guacenc_parse_timestamp(const char* str) {
|
||||
|
||||
int sign = 1;
|
||||
int64_t num = 0;
|
||||
|
||||
for (; *str != '\0'; str++) {
|
||||
|
||||
/* Flip sign for each '-' encountered */
|
||||
if (*str == '-')
|
||||
sign = -sign;
|
||||
|
||||
/* If not '-', assume the character is a digit */
|
||||
else
|
||||
num = num * 10 + (*str - '0');
|
||||
|
||||
}
|
||||
|
||||
return (guac_timestamp) (num * sign);
|
||||
|
||||
}
|
||||
|
||||
int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) {
|
||||
|
||||
/* Verify argument count */
|
||||
|
@ -30,6 +30,7 @@ guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = {
|
||||
{"blob", guacenc_handle_blob},
|
||||
{"img", guacenc_handle_img},
|
||||
{"end", guacenc_handle_end},
|
||||
{"mouse", guacenc_handle_mouse},
|
||||
{"sync", guacenc_handle_sync},
|
||||
{"cursor", guacenc_handle_cursor},
|
||||
{"copy", guacenc_handle_copy},
|
||||
|
@ -114,6 +114,11 @@ guacenc_instruction_handler guacenc_handle_img;
|
||||
*/
|
||||
guacenc_instruction_handler guacenc_handle_end;
|
||||
|
||||
/**
|
||||
* Handler for the Guacamole "mouse" instruction.
|
||||
*/
|
||||
guacenc_instruction_handler guacenc_handle_mouse;
|
||||
|
||||
/**
|
||||
* Handler for the Guacamole "sync" instruction.
|
||||
*/
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
@ -67,3 +69,24 @@ int guacenc_parse_dimensions(char* arg, int* width, int* height) {
|
||||
|
||||
}
|
||||
|
||||
guac_timestamp guacenc_parse_timestamp(const char* str) {
|
||||
|
||||
int sign = 1;
|
||||
int64_t num = 0;
|
||||
|
||||
for (; *str != '\0'; str++) {
|
||||
|
||||
/* Flip sign for each '-' encountered */
|
||||
if (*str == '-')
|
||||
sign = -sign;
|
||||
|
||||
/* If not '-', assume the character is a digit */
|
||||
else
|
||||
num = num * 10 + (*str - '0');
|
||||
|
||||
}
|
||||
|
||||
return (guac_timestamp) (num * sign);
|
||||
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
/**
|
||||
* Parses a string into a single integer. Only positive integers are accepted.
|
||||
* The input string may be modified during parsing. A value will be stored in
|
||||
@ -63,6 +65,21 @@ int guacenc_parse_int(char* arg, int* i);
|
||||
*/
|
||||
int guacenc_parse_dimensions(char* arg, int* width, int* height);
|
||||
|
||||
/**
|
||||
* Parses a guac_timestamp from the given string. The string is assumed to
|
||||
* consist solely of decimal digits with an optional leading minus sign. If the
|
||||
* given string contains other characters, the behavior of this function is
|
||||
* undefined.
|
||||
*
|
||||
* @param str
|
||||
* The string to parse, which must contain only decimal digits and an
|
||||
* optional leading minus sign.
|
||||
*
|
||||
* @return
|
||||
* A guac_timestamp having the same value as the provided string.
|
||||
*/
|
||||
guac_timestamp guacenc_parse_timestamp(const char* str);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -159,10 +159,28 @@ int vguac_protocol_send_log(guac_socket* socket, const char* format,
|
||||
* @param y
|
||||
* The Y coordinate of the current mouse position.
|
||||
*
|
||||
* @param button_mask
|
||||
* An integer value representing the current state of each button, where
|
||||
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
|
||||
* button is currently pressed. The lowest-order bit is the left mouse
|
||||
* button, followed by the middle button, right button, and finally the up
|
||||
* and down buttons of the scroll wheel.
|
||||
*
|
||||
* @see GUAC_CLIENT_MOUSE_LEFT
|
||||
* @see GUAC_CLIENT_MOUSE_MIDDLE
|
||||
* @see GUAC_CLIENT_MOUSE_RIGHT
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
|
||||
*
|
||||
* @param timestamp
|
||||
* The server timestamp (in milliseconds) at the point in time this mouse
|
||||
* position was acknowledged.
|
||||
*
|
||||
* @return
|
||||
* Zero on success, non-zero on error.
|
||||
*/
|
||||
int guac_protocol_send_mouse(guac_socket* socket, int x, int y);
|
||||
int guac_protocol_send_mouse(guac_socket* socket, int x, int y,
|
||||
int button_mask, guac_timestamp timestamp);
|
||||
|
||||
/**
|
||||
* Sends a nest instruction over the given guac_socket connection.
|
||||
|
@ -684,7 +684,8 @@ int guac_protocol_send_lstroke(guac_socket* socket,
|
||||
|
||||
}
|
||||
|
||||
int guac_protocol_send_mouse(guac_socket* socket, int x, int y) {
|
||||
int guac_protocol_send_mouse(guac_socket* socket, int x, int y,
|
||||
int button_mask, guac_timestamp timestamp) {
|
||||
|
||||
int ret_val;
|
||||
|
||||
@ -694,6 +695,10 @@ int guac_protocol_send_mouse(guac_socket* socket, int x, int y) {
|
||||
|| __guac_socket_write_length_int(socket, x)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, y)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, button_mask)
|
||||
|| guac_socket_write_string(socket, ",")
|
||||
|| __guac_socket_write_length_int(socket, timestamp)
|
||||
|| guac_socket_write_string(socket, ";");
|
||||
|
||||
guac_socket_instruction_end(socket);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "audio_input.h"
|
||||
#include "common/recording.h"
|
||||
#include "client.h"
|
||||
#include "rdp.h"
|
||||
#include "rdp_disp.h"
|
||||
@ -122,6 +123,10 @@ int guac_rdp_client_free_handler(guac_client* client) {
|
||||
guac_common_ssh_uninit();
|
||||
#endif
|
||||
|
||||
/* Clean up recording, if in progress */
|
||||
if (rdp_client->recording != NULL)
|
||||
guac_common_recording_free(rdp_client->recording);
|
||||
|
||||
/* Clean up audio stream, if allocated */
|
||||
if (rdp_client->audio != NULL)
|
||||
guac_audio_stream_free(rdp_client->audio);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "client.h"
|
||||
#include "common/recording.h"
|
||||
#include "input.h"
|
||||
#include "keyboard.h"
|
||||
#include "rdp.h"
|
||||
@ -46,8 +47,12 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Store current mouse location */
|
||||
guac_common_cursor_move(rdp_client->display->cursor, user, x, y);
|
||||
/* Store current mouse location/state */
|
||||
guac_common_cursor_update(rdp_client->display->cursor, user, x, y, mask);
|
||||
|
||||
/* Report mouse position within recording */
|
||||
if (rdp_client->recording != NULL)
|
||||
guac_common_recording_report_mouse(rdp_client->recording, x, y, mask);
|
||||
|
||||
/* If button mask unchanged, just send move event */
|
||||
if (mask == rdp_client->mouse_button_mask)
|
||||
|
@ -668,7 +668,7 @@ static int guac_rdp_handle_connection(guac_client* client) {
|
||||
|
||||
/* Set up screen recording, if requested */
|
||||
if (settings->recording_path != NULL) {
|
||||
guac_common_recording_create(client,
|
||||
rdp_client->recording = guac_common_recording_create(client,
|
||||
settings->recording_path,
|
||||
settings->recording_name,
|
||||
settings->create_recording_path);
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "common/clipboard.h"
|
||||
#include "common/display.h"
|
||||
#include "common/list.h"
|
||||
#include "common/recording.h"
|
||||
#include "common/surface.h"
|
||||
#include "keyboard.h"
|
||||
#include "rdp_disp.h"
|
||||
@ -143,6 +144,12 @@ typedef struct guac_rdp_client {
|
||||
guac_common_ssh_sftp_filesystem* sftp_filesystem;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The in-progress session recording, or NULL if no recording is in
|
||||
* progress.
|
||||
*/
|
||||
guac_common_recording* recording;
|
||||
|
||||
/**
|
||||
* Display size update module.
|
||||
*/
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "client.h"
|
||||
#include "common/recording.h"
|
||||
#include "common-ssh/sftp.h"
|
||||
#include "ssh.h"
|
||||
#include "terminal/terminal.h"
|
||||
@ -88,6 +89,10 @@ int guac_ssh_client_free_handler(guac_client* client) {
|
||||
guac_common_ssh_destroy_session(ssh_client->sftp_session);
|
||||
}
|
||||
|
||||
/* Clean up recording, if in progress */
|
||||
if (ssh_client->recording != NULL)
|
||||
guac_common_recording_free(ssh_client->recording);
|
||||
|
||||
/* Free interactive SSH session */
|
||||
if (ssh_client->session != NULL)
|
||||
guac_common_ssh_destroy_session(ssh_client->session);
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "common/cursor.h"
|
||||
#include "common/display.h"
|
||||
#include "common/recording.h"
|
||||
#include "ssh.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
@ -40,6 +41,10 @@ int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask) {
|
||||
if (term == NULL)
|
||||
return 0;
|
||||
|
||||
/* Report mouse position within recording */
|
||||
if (ssh_client->recording != NULL)
|
||||
guac_common_recording_report_mouse(ssh_client->recording, x, y, mask);
|
||||
|
||||
/* Send mouse event */
|
||||
guac_terminal_send_mouse(term, user, x, y, mask);
|
||||
return 0;
|
||||
|
@ -193,7 +193,7 @@ void* ssh_client_thread(void* data) {
|
||||
|
||||
/* Set up screen recording, if requested */
|
||||
if (settings->recording_path != NULL) {
|
||||
guac_common_recording_create(client,
|
||||
ssh_client->recording = guac_common_recording_create(client,
|
||||
settings->recording_path,
|
||||
settings->recording_name,
|
||||
settings->create_recording_path);
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/recording.h"
|
||||
#include "common-ssh/sftp.h"
|
||||
#include "common-ssh/ssh.h"
|
||||
#include "common-ssh/user.h"
|
||||
@ -93,6 +94,12 @@ typedef struct guac_ssh_client {
|
||||
*/
|
||||
guac_terminal* term;
|
||||
|
||||
/**
|
||||
* The in-progress session recording, or NULL if no recording is in
|
||||
* progress.
|
||||
*/
|
||||
guac_common_recording* recording;
|
||||
|
||||
} guac_ssh_client ;
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "client.h"
|
||||
#include "common/recording.h"
|
||||
#include "settings.h"
|
||||
#include "telnet.h"
|
||||
#include "terminal/terminal.h"
|
||||
@ -71,6 +72,10 @@ int guac_telnet_client_free_handler(guac_client* client) {
|
||||
if (telnet_client->socket_fd != -1)
|
||||
close(telnet_client->socket_fd);
|
||||
|
||||
/* Clean up recording, if in progress */
|
||||
if (telnet_client->recording != NULL)
|
||||
guac_common_recording_free(telnet_client->recording);
|
||||
|
||||
/* Kill terminal */
|
||||
guac_terminal_free(telnet_client->term);
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "common/recording.h"
|
||||
#include "input.h"
|
||||
#include "terminal/terminal.h"
|
||||
#include "telnet.h"
|
||||
@ -42,6 +43,11 @@ int guac_telnet_user_mouse_handler(guac_user* user, int x, int y, int mask) {
|
||||
if (term == NULL)
|
||||
return 0;
|
||||
|
||||
/* Report mouse position within recording */
|
||||
if (telnet_client->recording != NULL)
|
||||
guac_common_recording_report_mouse(telnet_client->recording, x, y,
|
||||
mask);
|
||||
|
||||
/* Send mouse if not searching for password or username */
|
||||
if (settings->password_regex == NULL && settings->username_regex == NULL)
|
||||
guac_terminal_send_mouse(term, user, x, y, mask);
|
||||
|
@ -467,7 +467,7 @@ void* guac_telnet_client_thread(void* data) {
|
||||
|
||||
/* Set up screen recording, if requested */
|
||||
if (settings->recording_path != NULL) {
|
||||
guac_common_recording_create(client,
|
||||
telnet_client->recording = guac_common_recording_create(client,
|
||||
settings->recording_path,
|
||||
settings->recording_name,
|
||||
settings->create_recording_path);
|
||||
|
@ -21,6 +21,7 @@
|
||||
#define GUAC_TELNET_H
|
||||
|
||||
#include "config.h"
|
||||
#include "common/recording.h"
|
||||
#include "settings.h"
|
||||
#include "terminal/terminal.h"
|
||||
|
||||
@ -69,7 +70,13 @@ typedef struct guac_telnet_client {
|
||||
* The terminal which will render all output from the telnet client.
|
||||
*/
|
||||
guac_terminal* term;
|
||||
|
||||
|
||||
/**
|
||||
* The in-progress session recording, or NULL if no recording is in
|
||||
* progress.
|
||||
*/
|
||||
guac_common_recording* recording;
|
||||
|
||||
} guac_telnet_client;
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/recording.h"
|
||||
#include "client.h"
|
||||
#include "user.h"
|
||||
#include "vnc.h"
|
||||
@ -102,6 +103,10 @@ int guac_vnc_client_free_handler(guac_client* client) {
|
||||
guac_common_ssh_uninit();
|
||||
#endif
|
||||
|
||||
/* Clean up recording, if in progress */
|
||||
if (vnc_client->recording != NULL)
|
||||
guac_common_recording_free(vnc_client->recording);
|
||||
|
||||
/* Free clipboard */
|
||||
if (vnc_client->clipboard != NULL)
|
||||
guac_common_clipboard_free(vnc_client->clipboard);
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "common/cursor.h"
|
||||
#include "common/display.h"
|
||||
#include "common/recording.h"
|
||||
#include "vnc.h"
|
||||
|
||||
#include <guacamole/user.h>
|
||||
@ -32,8 +33,12 @@ int guac_vnc_user_mouse_handler(guac_user* user, int x, int y, int mask) {
|
||||
guac_vnc_client* vnc_client = (guac_vnc_client*) client->data;
|
||||
rfbClient* rfb_client = vnc_client->rfb_client;
|
||||
|
||||
/* Store current mouse location */
|
||||
guac_common_cursor_move(vnc_client->display->cursor, user, x, y);
|
||||
/* Store current mouse location/state */
|
||||
guac_common_cursor_update(vnc_client->display->cursor, user, x, y, mask);
|
||||
|
||||
/* Report mouse position within recording */
|
||||
if (vnc_client->recording != NULL)
|
||||
guac_common_recording_report_mouse(vnc_client->recording, x, y, mask);
|
||||
|
||||
/* Send VNC event only if finished connecting */
|
||||
if (rfb_client != NULL)
|
||||
|
@ -303,7 +303,7 @@ void* guac_vnc_client_thread(void* data) {
|
||||
|
||||
/* Set up screen recording, if requested */
|
||||
if (settings->recording_path != NULL) {
|
||||
guac_common_recording_create(client,
|
||||
vnc_client->recording = guac_common_recording_create(client,
|
||||
settings->recording_path,
|
||||
settings->recording_name,
|
||||
settings->create_recording_path);
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "common/clipboard.h"
|
||||
#include "common/display.h"
|
||||
#include "common/iconv.h"
|
||||
#include "common/recording.h"
|
||||
#include "common/surface.h"
|
||||
#include "settings.h"
|
||||
|
||||
@ -110,6 +111,12 @@ typedef struct guac_vnc_client {
|
||||
guac_common_ssh_sftp_filesystem* sftp_filesystem;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The in-progress session recording, or NULL if no recording is in
|
||||
* progress.
|
||||
*/
|
||||
guac_common_recording* recording;
|
||||
|
||||
/**
|
||||
* Clipboard encoding-specific reader.
|
||||
*/
|
||||
|
@ -1670,8 +1670,8 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user,
|
||||
int released_mask = term->mouse_mask & ~mask;
|
||||
int pressed_mask = ~term->mouse_mask & mask;
|
||||
|
||||
/* Store current mouse location */
|
||||
guac_common_cursor_move(term->cursor, user, x, y);
|
||||
/* Store current mouse location/state */
|
||||
guac_common_cursor_update(term->cursor, user, x, y, mask);
|
||||
|
||||
/* Notify scrollbar, do not handle anything handled by scrollbar */
|
||||
if (guac_terminal_scrollbar_handle_mouse(term->scrollbar, x, y, mask)) {
|
||||
|
Loading…
Reference in New Issue
Block a user