mirror of
https://github.com/morgan9e/grd
synced 2026-04-14 00:14:18 +09:00
765 lines
24 KiB
C
765 lines
24 KiB
C
/*
|
|
* Copyright (C) 2025 Pascal Nowack
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "grd-rdp-dvc-input.h"
|
|
|
|
#include <freerdp/server/rdpei.h>
|
|
|
|
#include "grd-rdp-layout-manager.h"
|
|
|
|
#define MAX_TOUCH_CONTACTS 256
|
|
|
|
typedef enum
|
|
{
|
|
INPUT_EVENT_TYPE_TOUCH,
|
|
INPUT_EVENT_TYPE_DISMISS_HOVERING_TOUCH_CONTACT,
|
|
INPUT_EVENT_TYPE_PEN,
|
|
} InputEventType;
|
|
|
|
/*
|
|
* Contact states based on Touch Contact State Transitions
|
|
* ([MS-RDPEI] 3.1.1.1)
|
|
*/
|
|
typedef enum
|
|
{
|
|
CONTACT_STATE_OUT_OF_RANGE = 0,
|
|
CONTACT_STATE_HOVERING,
|
|
CONTACT_STATE_ENGAGED,
|
|
} ContactState;
|
|
|
|
typedef struct
|
|
{
|
|
InputEventType event_type;
|
|
|
|
/* INPUT_EVENT_TYPE_TOUCH */
|
|
RDPINPUT_TOUCH_FRAME touch_frame;
|
|
|
|
/* INPUT_EVENT_TYPE_DISMISS_HOVERING_TOUCH_CONTACT */
|
|
uint8_t touch_contact_to_dismiss;
|
|
|
|
/* INPUT_EVENT_TYPE_PEN */
|
|
/* Missing libei-API to submit (multi-)pen events */
|
|
} InputEvent;
|
|
|
|
typedef struct
|
|
{
|
|
ContactState contact_state;
|
|
|
|
GrdTouchContact *touch_contact;
|
|
gboolean ignore_contact;
|
|
} TouchContext;
|
|
|
|
struct _GrdRdpDvcInput
|
|
{
|
|
GrdRdpDvc parent;
|
|
|
|
RdpeiServerContext *rdpei_context;
|
|
gboolean channel_opened;
|
|
|
|
GrdRdpLayoutManager *layout_manager;
|
|
GrdSession *session;
|
|
|
|
GSource *event_source;
|
|
GAsyncQueue *event_queue;
|
|
|
|
gboolean pending_touch_device_frame;
|
|
GList *touch_contacts_to_dispose;
|
|
|
|
TouchContext touch_contexts[MAX_TOUCH_CONTACTS];
|
|
|
|
unsigned long touch_device_added_id;
|
|
unsigned long touch_device_removed_id;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GrdRdpDvcInput, grd_rdp_dvc_input,
|
|
GRD_TYPE_RDP_DVC)
|
|
|
|
static void
|
|
grd_rdp_dvc_input_maybe_init (GrdRdpDvc *dvc)
|
|
{
|
|
GrdRdpDvcInput *input = GRD_RDP_DVC_INPUT (dvc);
|
|
RdpeiServerContext *rdpei_context;
|
|
|
|
if (input->channel_opened)
|
|
return;
|
|
|
|
rdpei_context = input->rdpei_context;
|
|
if (rdpei_context->Open (rdpei_context))
|
|
{
|
|
g_warning ("[RDP.INPUT] Failed to open channel. "
|
|
"Terminating protocol");
|
|
grd_rdp_dvc_queue_channel_tear_down (GRD_RDP_DVC (input));
|
|
return;
|
|
}
|
|
input->channel_opened = TRUE;
|
|
}
|
|
|
|
static void
|
|
dvc_creation_status (gpointer user_data,
|
|
int32_t creation_status)
|
|
{
|
|
GrdRdpDvcInput *input = user_data;
|
|
RdpeiServerContext *rdpei_context = input->rdpei_context;
|
|
|
|
if (creation_status < 0)
|
|
{
|
|
g_debug ("[RDP.INPUT] Failed to open channel (CreationStatus %i). "
|
|
"Terminating protocol", creation_status);
|
|
grd_rdp_dvc_queue_channel_tear_down (GRD_RDP_DVC (input));
|
|
return;
|
|
}
|
|
|
|
rdpei_server_send_sc_ready (rdpei_context, RDPINPUT_PROTOCOL_V300,
|
|
SC_READY_MULTIPEN_INJECTION_SUPPORTED);
|
|
}
|
|
|
|
static BOOL
|
|
input_channel_id_assigned (RdpeiServerContext *rdpei_context,
|
|
uint32_t channel_id)
|
|
{
|
|
GrdRdpDvcInput *input = rdpei_context->user_data;
|
|
GrdRdpDvc *dvc = GRD_RDP_DVC (input);
|
|
|
|
g_debug ("[RDP.INPUT] DVC channel id assigned to id %u", channel_id);
|
|
|
|
grd_rdp_dvc_subscribe_creation_status (dvc, channel_id,
|
|
dvc_creation_status,
|
|
input);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static uint32_t
|
|
input_client_ready (RdpeiServerContext *rdpei_context)
|
|
{
|
|
g_message ("[RDP.INPUT] Client version: 0x%08X, Flags: 0x%08X, "
|
|
"Maximum simultaneous touch contacts: %u",
|
|
rdpei_context->clientVersion, rdpei_context->protocolFlags,
|
|
rdpei_context->maxTouchPoints);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static uint32_t
|
|
input_touch (RdpeiServerContext *rdpei_context,
|
|
const RDPINPUT_TOUCH_EVENT *touch_event)
|
|
{
|
|
GrdRdpDvcInput *input = rdpei_context->user_data;
|
|
uint16_t i;
|
|
|
|
for (i = 0; i < touch_event->frameCount; ++i)
|
|
{
|
|
RDPINPUT_TOUCH_FRAME *touch_frame = &touch_event->frames[i];
|
|
InputEvent *input_event;
|
|
|
|
if (touch_frame->contactCount == 0)
|
|
continue;
|
|
|
|
input_event = g_new0 (InputEvent, 1);
|
|
input_event->event_type = INPUT_EVENT_TYPE_TOUCH;
|
|
input_event->touch_frame = *touch_frame;
|
|
input_event->touch_frame.contacts =
|
|
g_memdup2 (touch_frame->contacts, touch_frame->contactCount *
|
|
sizeof (RDPINPUT_CONTACT_DATA));
|
|
|
|
g_async_queue_push (input->event_queue, input_event);
|
|
g_source_set_ready_time (input->event_source, 0);
|
|
}
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static uint32_t
|
|
input_dismiss_hovering_touch_contact (RdpeiServerContext *rdpei_context,
|
|
uint8_t contact_id)
|
|
{
|
|
GrdRdpDvcInput *input = rdpei_context->user_data;
|
|
InputEvent *input_event;
|
|
|
|
input_event = g_new0 (InputEvent, 1);
|
|
input_event->event_type = INPUT_EVENT_TYPE_DISMISS_HOVERING_TOUCH_CONTACT;
|
|
input_event->touch_contact_to_dismiss = contact_id;
|
|
|
|
g_async_queue_push (input->event_queue, input_event);
|
|
g_source_set_ready_time (input->event_source, 0);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static uint32_t
|
|
input_pen (RdpeiServerContext *rdpei_context,
|
|
const RDPINPUT_PEN_EVENT *pen_event)
|
|
{
|
|
/* Missing libei-API to submit (multi-)pen events */
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static void
|
|
on_touch_device_added (GrdSession *session,
|
|
GrdRdpDvcInput *input)
|
|
{
|
|
g_source_set_ready_time (input->event_source, 0);
|
|
}
|
|
|
|
static void
|
|
on_touch_device_removed (GrdSession *session,
|
|
GrdRdpDvcInput *input)
|
|
{
|
|
uint16_t i;
|
|
|
|
for (i = 0; i < MAX_TOUCH_CONTACTS; ++i)
|
|
{
|
|
TouchContext *touch_context = &input->touch_contexts[i];
|
|
GrdTouchContact *touch_contact;
|
|
|
|
if (touch_context->contact_state == CONTACT_STATE_OUT_OF_RANGE)
|
|
continue;
|
|
|
|
g_debug ("[RDP.INPUT] Resetting touch contact %u", i);
|
|
|
|
touch_contact = g_steal_pointer (&touch_context->touch_contact);
|
|
grd_session_release_touch_contact (session, touch_contact);
|
|
|
|
touch_context->contact_state = CONTACT_STATE_OUT_OF_RANGE;
|
|
|
|
/*
|
|
* When the monitor layout changes, Windows App for the iPhone resets
|
|
* each contact state. This also applies to touch contacts, which are
|
|
* still pressed by the user.
|
|
* However, for some reason, this client still continues to send
|
|
* touch-motion events for the reset touch contact. The touch position
|
|
* in these events is always the same and does not reflect the actual
|
|
* reality.
|
|
* This continues to happen until the user taps again, in which case the
|
|
* client stops these spurious touch-motion events and sends a
|
|
* touch-down event.
|
|
* So, ignore these spurious touch-motion events until a valid
|
|
* contact-flag combination is sent for the 'Out of Range'-state.
|
|
*/
|
|
touch_context->ignore_contact = TRUE;
|
|
}
|
|
}
|
|
|
|
GrdRdpDvcInput *
|
|
grd_rdp_dvc_input_new (GrdRdpLayoutManager *layout_manager,
|
|
GrdSessionRdp *session_rdp,
|
|
GrdRdpDvcHandler *dvc_handler,
|
|
HANDLE vcm)
|
|
{
|
|
GrdRdpDvcInput *input;
|
|
RdpeiServerContext *rdpei_context;
|
|
|
|
input = g_object_new (GRD_TYPE_RDP_DVC_INPUT, NULL);
|
|
rdpei_context = rdpei_server_context_new (vcm);
|
|
if (!rdpei_context)
|
|
g_error ("[RDP.INPUT] Failed to allocate server context (OOM)");
|
|
|
|
input->rdpei_context = rdpei_context;
|
|
input->layout_manager = layout_manager;
|
|
input->session = GRD_SESSION (session_rdp);
|
|
|
|
grd_rdp_dvc_initialize_base (GRD_RDP_DVC (input),
|
|
dvc_handler, session_rdp,
|
|
GRD_RDP_CHANNEL_INPUT);
|
|
|
|
rdpei_context->onChannelIdAssigned = input_channel_id_assigned;
|
|
rdpei_context->onClientReady = input_client_ready;
|
|
rdpei_context->onTouchEvent = input_touch;
|
|
rdpei_context->onTouchReleased = input_dismiss_hovering_touch_contact;
|
|
rdpei_context->onPenEvent = input_pen;
|
|
rdpei_context->user_data = input;
|
|
|
|
input->touch_device_added_id =
|
|
g_signal_connect (input->session, "touch-device-added",
|
|
G_CALLBACK (on_touch_device_added),
|
|
input);
|
|
input->touch_device_removed_id =
|
|
g_signal_connect (input->session, "touch-device-removed",
|
|
G_CALLBACK (on_touch_device_removed),
|
|
input);
|
|
|
|
return input;
|
|
}
|
|
|
|
static void
|
|
ensure_queued_touch_device_frame (GrdRdpDvcInput *input)
|
|
{
|
|
input->pending_touch_device_frame = TRUE;
|
|
}
|
|
|
|
static void
|
|
queue_touch_contact_disposal (GrdRdpDvcInput *input,
|
|
TouchContext *touch_context)
|
|
{
|
|
GrdTouchContact *touch_contact;
|
|
|
|
g_assert (touch_context->touch_contact);
|
|
|
|
touch_contact = g_steal_pointer (&touch_context->touch_contact);
|
|
input->touch_contacts_to_dispose =
|
|
g_list_prepend (input->touch_contacts_to_dispose, touch_contact);
|
|
|
|
touch_context->ignore_contact = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Automata based on Touch Contact State Transitions
|
|
* ([MS-RDPEI] 3.1.1.1)
|
|
*/
|
|
static void
|
|
handle_touch_state_out_of_range (GrdRdpDvcInput *input,
|
|
RDPINPUT_CONTACT_DATA *touch_contact_data)
|
|
{
|
|
uint8_t contact_id = touch_contact_data->contactId;
|
|
TouchContext *touch_context = &input->touch_contexts[contact_id];
|
|
GrdSession *session = input->session;
|
|
|
|
g_assert (!touch_context->touch_contact);
|
|
|
|
if (touch_contact_data->contactFlags == (RDPINPUT_CONTACT_FLAG_DOWN |
|
|
RDPINPUT_CONTACT_FLAG_INRANGE |
|
|
RDPINPUT_CONTACT_FLAG_INCONTACT))
|
|
{
|
|
GrdRdpLayoutManager *layout_manager = input->layout_manager;
|
|
GrdEventMotionAbs motion_abs = {};
|
|
GrdStream *stream = NULL;
|
|
|
|
touch_context->touch_contact =
|
|
grd_session_acquire_touch_contact (session);
|
|
touch_context->ignore_contact = FALSE;
|
|
|
|
if (grd_rdp_layout_manager_transform_position (layout_manager,
|
|
touch_contact_data->x,
|
|
touch_contact_data->y,
|
|
&stream, &motion_abs))
|
|
{
|
|
grd_session_notify_touch_down (session, touch_context->touch_contact,
|
|
stream, &motion_abs);
|
|
ensure_queued_touch_device_frame (input);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* The client does not know, when a specific touch contact is useless
|
|
* due to its position not being transformable. As a result, it will
|
|
* deem it as any other touch contact. So ignore all assigned actions
|
|
* and just let the touch contact run through all automata states.
|
|
*
|
|
* See also 3.2.5.3 Processing an RDPINPUT_TOUCH_EVENT_PDU Message
|
|
* ([MS-RDPEI])
|
|
*/
|
|
touch_context->ignore_contact = TRUE;
|
|
}
|
|
|
|
touch_context->contact_state = CONTACT_STATE_ENGAGED;
|
|
}
|
|
else if (touch_contact_data->contactFlags == (RDPINPUT_CONTACT_FLAG_UPDATE |
|
|
RDPINPUT_CONTACT_FLAG_INRANGE))
|
|
{
|
|
touch_context->touch_contact =
|
|
grd_session_acquire_touch_contact (session);
|
|
touch_context->ignore_contact = FALSE;
|
|
|
|
touch_context->contact_state = CONTACT_STATE_HOVERING;
|
|
}
|
|
else if (!touch_context->ignore_contact)
|
|
{
|
|
g_warning ("[RDP.INPUT] Protocol violation: Client sent invalid contact "
|
|
"flags 0x%08X in state 'Out of Range' for contact %u",
|
|
touch_contact_data->contactFlags, contact_id);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Automata based on Touch Contact State Transitions
|
|
* ([MS-RDPEI] 3.1.1.1)
|
|
*/
|
|
static void
|
|
handle_touch_state_hovering (GrdRdpDvcInput *input,
|
|
RDPINPUT_CONTACT_DATA *touch_contact_data)
|
|
{
|
|
uint8_t contact_id = touch_contact_data->contactId;
|
|
TouchContext *touch_context = &input->touch_contexts[contact_id];
|
|
|
|
g_assert (touch_context->touch_contact);
|
|
|
|
if (touch_contact_data->contactFlags == (RDPINPUT_CONTACT_FLAG_DOWN |
|
|
RDPINPUT_CONTACT_FLAG_INRANGE |
|
|
RDPINPUT_CONTACT_FLAG_INCONTACT))
|
|
{
|
|
GrdRdpLayoutManager *layout_manager = input->layout_manager;
|
|
GrdSession *session = input->session;
|
|
GrdEventMotionAbs motion_abs = {};
|
|
GrdStream *stream = NULL;
|
|
|
|
if (grd_rdp_layout_manager_transform_position (layout_manager,
|
|
touch_contact_data->x,
|
|
touch_contact_data->y,
|
|
&stream, &motion_abs))
|
|
{
|
|
grd_session_notify_touch_down (session, touch_context->touch_contact,
|
|
stream, &motion_abs);
|
|
ensure_queued_touch_device_frame (input);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* The client does not know, when a specific touch contact is useless
|
|
* due to its position not being transformable. As a result, it will
|
|
* deem it as any other touch contact. So ignore all assigned actions
|
|
* and just let the touch contact run through all automata states.
|
|
*
|
|
* See also 3.2.5.3 Processing an RDPINPUT_TOUCH_EVENT_PDU Message
|
|
* ([MS-RDPEI])
|
|
*/
|
|
touch_context->ignore_contact = TRUE;
|
|
}
|
|
|
|
touch_context->contact_state = CONTACT_STATE_ENGAGED;
|
|
}
|
|
else if (touch_contact_data->contactFlags == (RDPINPUT_CONTACT_FLAG_UPDATE |
|
|
RDPINPUT_CONTACT_FLAG_INRANGE))
|
|
{
|
|
touch_context->contact_state = CONTACT_STATE_HOVERING;
|
|
}
|
|
else if (touch_contact_data->contactFlags == (RDPINPUT_CONTACT_FLAG_UPDATE |
|
|
RDPINPUT_CONTACT_FLAG_CANCELED))
|
|
{
|
|
queue_touch_contact_disposal (input, touch_context);
|
|
|
|
touch_context->contact_state = CONTACT_STATE_OUT_OF_RANGE;
|
|
}
|
|
else if (touch_contact_data->contactFlags == RDPINPUT_CONTACT_FLAG_UPDATE)
|
|
{
|
|
queue_touch_contact_disposal (input, touch_context);
|
|
|
|
touch_context->contact_state = CONTACT_STATE_OUT_OF_RANGE;
|
|
}
|
|
else
|
|
{
|
|
g_warning ("[RDP.INPUT] Protocol violation: Client sent invalid contact "
|
|
"flags 0x%08X in state 'Hovering' for contact %u",
|
|
touch_contact_data->contactFlags, contact_id);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Automata based on Touch Contact State Transitions
|
|
* ([MS-RDPEI] 3.1.1.1)
|
|
*/
|
|
static void
|
|
handle_touch_state_engaged (GrdRdpDvcInput *input,
|
|
RDPINPUT_CONTACT_DATA *touch_contact_data)
|
|
{
|
|
uint8_t contact_id = touch_contact_data->contactId;
|
|
TouchContext *touch_context = &input->touch_contexts[contact_id];
|
|
GrdSession *session = input->session;
|
|
|
|
g_assert (touch_context->touch_contact);
|
|
|
|
if (touch_contact_data->contactFlags == (RDPINPUT_CONTACT_FLAG_UPDATE |
|
|
RDPINPUT_CONTACT_FLAG_INRANGE |
|
|
RDPINPUT_CONTACT_FLAG_INCONTACT))
|
|
{
|
|
GrdRdpLayoutManager *layout_manager = input->layout_manager;
|
|
GrdEventMotionAbs motion_abs = {};
|
|
GrdStream *stream = NULL;
|
|
|
|
if (touch_context->ignore_contact ||
|
|
!grd_rdp_layout_manager_transform_position (layout_manager,
|
|
touch_contact_data->x,
|
|
touch_contact_data->y,
|
|
&stream, &motion_abs))
|
|
return;
|
|
|
|
grd_session_notify_touch_motion (session, touch_context->touch_contact,
|
|
stream, &motion_abs);
|
|
ensure_queued_touch_device_frame (input);
|
|
|
|
touch_context->contact_state = CONTACT_STATE_ENGAGED;
|
|
}
|
|
else if (touch_contact_data->contactFlags == (RDPINPUT_CONTACT_FLAG_UP |
|
|
RDPINPUT_CONTACT_FLAG_INRANGE))
|
|
{
|
|
if (!touch_context->ignore_contact)
|
|
{
|
|
grd_session_notify_touch_up (session, touch_context->touch_contact);
|
|
ensure_queued_touch_device_frame (input);
|
|
}
|
|
|
|
queue_touch_contact_disposal (input, touch_context);
|
|
touch_context->touch_contact =
|
|
grd_session_acquire_touch_contact (session);
|
|
|
|
touch_context->contact_state = CONTACT_STATE_HOVERING;
|
|
}
|
|
else if (touch_contact_data->contactFlags == (RDPINPUT_CONTACT_FLAG_UP |
|
|
RDPINPUT_CONTACT_FLAG_CANCELED))
|
|
{
|
|
if (!touch_context->ignore_contact)
|
|
{
|
|
grd_session_notify_touch_cancel (session, touch_context->touch_contact);
|
|
ensure_queued_touch_device_frame (input);
|
|
}
|
|
queue_touch_contact_disposal (input, touch_context);
|
|
|
|
touch_context->contact_state = CONTACT_STATE_OUT_OF_RANGE;
|
|
}
|
|
else if (touch_contact_data->contactFlags == RDPINPUT_CONTACT_FLAG_UP)
|
|
{
|
|
if (!touch_context->ignore_contact)
|
|
{
|
|
grd_session_notify_touch_up (session, touch_context->touch_contact);
|
|
ensure_queued_touch_device_frame (input);
|
|
}
|
|
queue_touch_contact_disposal (input, touch_context);
|
|
|
|
touch_context->contact_state = CONTACT_STATE_OUT_OF_RANGE;
|
|
}
|
|
else
|
|
{
|
|
g_warning ("[RDP.INPUT] Protocol violation: Client sent invalid contact "
|
|
"flags 0x%08X in state 'Engaged' for contact %u",
|
|
touch_contact_data->contactFlags, contact_id);
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_touch_contact (GrdRdpDvcInput *input,
|
|
RDPINPUT_CONTACT_DATA *touch_contact_data)
|
|
{
|
|
uint8_t contact_id = touch_contact_data->contactId;
|
|
TouchContext *touch_context = &input->touch_contexts[contact_id];
|
|
|
|
/*
|
|
* The optional contact-rect, orientation, and pressure fields are unhandled
|
|
* due to missing libei-API to submit them
|
|
*/
|
|
|
|
switch (touch_context->contact_state)
|
|
{
|
|
case CONTACT_STATE_OUT_OF_RANGE:
|
|
handle_touch_state_out_of_range (input, touch_contact_data);
|
|
break;
|
|
case CONTACT_STATE_HOVERING:
|
|
handle_touch_state_hovering (input, touch_contact_data);
|
|
break;
|
|
case CONTACT_STATE_ENGAGED:
|
|
handle_touch_state_engaged (input, touch_contact_data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
maybe_notify_touch_device_frame (GrdRdpDvcInput *input)
|
|
{
|
|
GrdSession *session = input->session;
|
|
|
|
if (!input->pending_touch_device_frame)
|
|
return;
|
|
|
|
grd_session_notify_touch_device_frame (session);
|
|
input->pending_touch_device_frame = FALSE;
|
|
}
|
|
|
|
static void
|
|
dispose_touch_contacts (GrdRdpDvcInput *input)
|
|
{
|
|
GrdSession *session = input->session;
|
|
GList *l;
|
|
|
|
for (l = input->touch_contacts_to_dispose; l; l = l->next)
|
|
{
|
|
GrdTouchContact *touch_contact = l->data;
|
|
|
|
grd_session_release_touch_contact (session, touch_contact);
|
|
}
|
|
g_clear_pointer (&input->touch_contacts_to_dispose, g_list_free);
|
|
}
|
|
|
|
static void
|
|
process_touch_frame (GrdRdpDvcInput *input,
|
|
RDPINPUT_TOUCH_FRAME *touch_frame)
|
|
{
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < touch_frame->contactCount; ++i)
|
|
process_touch_contact (input, &touch_frame->contacts[i]);
|
|
|
|
maybe_notify_touch_device_frame (input);
|
|
dispose_touch_contacts (input);
|
|
}
|
|
|
|
static void
|
|
dismiss_touch_contact (GrdRdpDvcInput *input,
|
|
uint8_t contact_id)
|
|
{
|
|
TouchContext *touch_context = &input->touch_contexts[contact_id];
|
|
|
|
if (!touch_context->touch_contact)
|
|
g_assert (touch_context->contact_state == CONTACT_STATE_OUT_OF_RANGE);
|
|
|
|
/* Client did not keep track of touch contact properly */
|
|
if (!touch_context->touch_contact)
|
|
return;
|
|
|
|
g_assert (touch_context->contact_state != CONTACT_STATE_OUT_OF_RANGE);
|
|
|
|
if (touch_context->contact_state == CONTACT_STATE_ENGAGED)
|
|
{
|
|
GrdSession *session = input->session;
|
|
|
|
grd_session_notify_touch_cancel (session, touch_context->touch_contact);
|
|
ensure_queued_touch_device_frame (input);
|
|
}
|
|
queue_touch_contact_disposal (input, touch_context);
|
|
|
|
maybe_notify_touch_device_frame (input);
|
|
dispose_touch_contacts (input);
|
|
|
|
touch_context->contact_state = CONTACT_STATE_OUT_OF_RANGE;
|
|
}
|
|
|
|
static void
|
|
input_event_free (InputEvent *input_event)
|
|
{
|
|
g_clear_pointer (&input_event->touch_frame.contacts, g_free);
|
|
|
|
g_free (input_event);
|
|
}
|
|
|
|
static gboolean
|
|
handle_input_events (gpointer user_data)
|
|
{
|
|
GrdRdpDvcInput *input = user_data;
|
|
InputEvent *input_event;
|
|
|
|
if (!grd_session_has_touch_device (input->session))
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
while ((input_event = g_async_queue_try_pop (input->event_queue)))
|
|
{
|
|
switch (input_event->event_type)
|
|
{
|
|
case INPUT_EVENT_TYPE_TOUCH:
|
|
process_touch_frame (input, &input_event->touch_frame);
|
|
break;
|
|
case INPUT_EVENT_TYPE_DISMISS_HOVERING_TOUCH_CONTACT:
|
|
dismiss_touch_contact (input, input_event->touch_contact_to_dismiss);
|
|
break;
|
|
case INPUT_EVENT_TYPE_PEN:
|
|
/* Missing libei-API to submit (multi-)pen events */
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
input_event_free (input_event);
|
|
}
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
grd_rdp_dvc_input_dispose (GObject *object)
|
|
{
|
|
GrdRdpDvcInput *input = GRD_RDP_DVC_INPUT (object);
|
|
GrdRdpDvc *dvc = GRD_RDP_DVC (input);
|
|
uint16_t i;
|
|
|
|
if (input->channel_opened)
|
|
{
|
|
input->rdpei_context->Close (input->rdpei_context);
|
|
input->channel_opened = FALSE;
|
|
}
|
|
grd_rdp_dvc_maybe_unsubscribe_creation_status (dvc);
|
|
|
|
g_clear_signal_handler (&input->touch_device_removed_id, input->session);
|
|
g_clear_signal_handler (&input->touch_device_added_id, input->session);
|
|
|
|
if (input->event_source)
|
|
{
|
|
g_source_destroy (input->event_source);
|
|
g_clear_pointer (&input->event_source, g_source_unref);
|
|
}
|
|
|
|
handle_input_events (input);
|
|
g_clear_pointer (&input->event_queue, g_async_queue_unref);
|
|
|
|
g_assert (!input->touch_contacts_to_dispose);
|
|
|
|
for (i = 0; i < MAX_TOUCH_CONTACTS; ++i)
|
|
{
|
|
TouchContext *touch_context = &input->touch_contexts[i];
|
|
GrdTouchContact *touch_contact;
|
|
|
|
touch_contact = g_steal_pointer (&touch_context->touch_contact);
|
|
if (touch_contact)
|
|
grd_session_release_touch_contact (input->session, touch_contact);
|
|
}
|
|
|
|
g_clear_pointer (&input->rdpei_context,
|
|
rdpei_server_context_free);
|
|
|
|
G_OBJECT_CLASS (grd_rdp_dvc_input_parent_class)->dispose (object);
|
|
}
|
|
|
|
static gboolean
|
|
event_source_dispatch (GSource *source,
|
|
GSourceFunc callback,
|
|
gpointer user_data)
|
|
{
|
|
g_source_set_ready_time (source, -1);
|
|
|
|
return callback (user_data);
|
|
}
|
|
|
|
static GSourceFuncs event_source_funcs =
|
|
{
|
|
.dispatch = event_source_dispatch,
|
|
};
|
|
|
|
static void
|
|
grd_rdp_dvc_input_init (GrdRdpDvcInput *input)
|
|
{
|
|
GSource *event_source;
|
|
|
|
input->event_queue = g_async_queue_new ();
|
|
|
|
event_source = g_source_new (&event_source_funcs, sizeof (GSource));
|
|
g_source_set_callback (event_source, handle_input_events,
|
|
input, NULL);
|
|
g_source_set_ready_time (event_source, -1);
|
|
g_source_attach (event_source, NULL);
|
|
input->event_source = event_source;
|
|
}
|
|
|
|
static void
|
|
grd_rdp_dvc_input_class_init (GrdRdpDvcInputClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GrdRdpDvcClass *dvc_class = GRD_RDP_DVC_CLASS (klass);
|
|
|
|
object_class->dispose = grd_rdp_dvc_input_dispose;
|
|
|
|
dvc_class->maybe_init = grd_rdp_dvc_input_maybe_init;
|
|
}
|