diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 9507ef9d7..c725665e7 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -28,6 +28,10 @@ if(FREERDP_VENDOR AND WITH_CLIENT) endif() endif() + if(WITH_CLIENT_SDL) + add_subdirectory(SDL) + endif() + if(WITH_X11) add_subdirectory(X11) endif() diff --git a/client/SDL/CMakeLists.txt b/client/SDL/CMakeLists.txt new file mode 100644 index 000000000..2baa76526 --- /dev/null +++ b/client/SDL/CMakeLists.txt @@ -0,0 +1,50 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2022 Armin Novak +# +# Licensed 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. + +set(MODULE_NAME "sdl-client") +set(MODULE_PREFIX "FREERDP_CLIENT_X11_CONTROL") + +find_package(SDL2 REQUIRED) +include_directories(${SDL2_INCLUDE_DIRS}) +message("xxxxxxxxxxxxx ${SDL2_INCLUDE_DIRS}") +set(SRCS + sdl_utils.c + sdl_utils.h + sdl_kbd.c + sdl_kbd.h + sdl_touch.c + sdl_touch.h + sdl_pointer.c + sdl_pointer.h + sdl_disp.c + sdl_disp.h + sdl_monitor.c + sdl_monitor.h + sdl_freerdp.h + sdl_freerdp.c + sdl_channels.h + sdl_channels.c) + +set(LIBS + ${SDL2_LIBRARIES} + freerdp-client) + +add_executable(${MODULE_NAME} ${SRCS}) +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "sdl-freerdp") +target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS}) +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/SDL") +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) diff --git a/client/SDL/sdl_channels.c b/client/SDL/sdl_channels.c new file mode 100644 index 000000000..97b07cd2a --- /dev/null +++ b/client/SDL/sdl_channels.c @@ -0,0 +1,79 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client Channels + * + * Copyright 2022 Armin Novak + * + * Licensed 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 + +#include + +#include +#include +#include + +#include "sdl_channels.h" +#include "sdl_freerdp.h" +#include "sdl_disp.h" + +void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + sdlContext* sdl = (sdlContext*)context; + + WINPR_ASSERT(sdl); + WINPR_ASSERT(e); + + if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + CliprdrClientContext* clip = (CliprdrClientContext*)e->pInterface; + WINPR_ASSERT(clip); + clip->custom = context; + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + sdl_disp_init(sdl->disp, (DispClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelConnectedEventHandler(context, e); +} + +void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e) +{ + sdlContext* sdl = (sdlContext*)context; + + WINPR_ASSERT(sdl); + WINPR_ASSERT(e); + + // TODO: Set resizeable depending on disp channel and /dynamic-resolution + if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + CliprdrClientContext* clip = (CliprdrClientContext*)e->pInterface; + WINPR_ASSERT(clip); + clip->custom = NULL; + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + sdl_disp_uninit(sdl->disp, (DispClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelDisconnectedEventHandler(context, e); +} diff --git a/client/SDL/sdl_channels.h b/client/SDL/sdl_channels.h new file mode 100644 index 000000000..5eb9f0c46 --- /dev/null +++ b/client/SDL/sdl_channels.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client Channels + * + * Copyright 2022 Armin Novak + * + * Licensed 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 FREERDP_CLIENT_SDL_CHANNELS_H +#define FREERDP_CLIENT_SDL_CHANNELS_H + +#include +#include + +int sdl_on_channel_connected(freerdp* instance, const char* name, void* pInterface); +int sdl_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface); + +void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_SDL_CHANNELS_H */ diff --git a/client/SDL/sdl_disp.c b/client/SDL/sdl_disp.c new file mode 100644 index 000000000..e59022d76 --- /dev/null +++ b/client/SDL/sdl_disp.c @@ -0,0 +1,536 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Display Control Channel + * + * Copyright 2023 Armin Novak + * + * Licensed 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 +#include + +#include + +#include + +#include "sdl_disp.h" +#include "sdl_kbd.h" + +#include +#define TAG CLIENT_TAG("sdl.disp") + +#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */ + +struct s_sdlDispContext +{ + sdlContext* sdl; + DispClientContext* disp; + int eventBase, errorBase; + int lastSentWidth, lastSentHeight; + UINT64 lastSentDate; + int targetWidth, targetHeight; + BOOL activated; + BOOL waitingResize; + BOOL fullscreen; + UINT16 lastSentDesktopOrientation; + UINT32 lastSentDesktopScaleFactor; + UINT32 lastSentDeviceScaleFactor; +}; + +static UINT sdl_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, + size_t nmonitors); + +static BOOL sdl_disp_settings_changed(sdlDispContext* sdlDisp) +{ + rdpSettings* settings; + + WINPR_ASSERT(sdlDisp); + WINPR_ASSERT(sdlDisp->sdl); + + settings = sdlDisp->sdl->common.context.settings; + WINPR_ASSERT(settings); + + if (sdlDisp->lastSentWidth != sdlDisp->targetWidth) + return TRUE; + + if (sdlDisp->lastSentHeight != sdlDisp->targetHeight) + return TRUE; + + if (sdlDisp->lastSentDesktopOrientation != settings->DesktopOrientation) + return TRUE; + + if (sdlDisp->lastSentDesktopScaleFactor != settings->DesktopScaleFactor) + return TRUE; + + if (sdlDisp->lastSentDeviceScaleFactor != settings->DeviceScaleFactor) + return TRUE; + /* TODFO + if (sdlDisp->fullscreen != sdlDisp->sdl->fullscreen) + return TRUE; + */ + return FALSE; +} + +static BOOL sdl_update_last_sent(sdlDispContext* sdlDisp) +{ + rdpSettings* settings; + + WINPR_ASSERT(sdlDisp); + WINPR_ASSERT(sdlDisp->sdl); + + settings = sdlDisp->sdl->common.context.settings; + WINPR_ASSERT(settings); + + sdlDisp->lastSentWidth = sdlDisp->targetWidth; + sdlDisp->lastSentHeight = sdlDisp->targetHeight; + sdlDisp->lastSentDesktopOrientation = settings->DesktopOrientation; + sdlDisp->lastSentDesktopScaleFactor = settings->DesktopScaleFactor; + sdlDisp->lastSentDeviceScaleFactor = settings->DeviceScaleFactor; + // TODO sdlDisp->fullscreen = sdlDisp->sdl->fullscreen; + return TRUE; +} + +static BOOL sdl_disp_sendResize(sdlDispContext* sdlDisp) +{ + DISPLAY_CONTROL_MONITOR_LAYOUT layout; + sdlContext* sdl; + rdpSettings* settings; + + if (!sdlDisp || !sdlDisp->sdl) + return FALSE; + + sdl = sdlDisp->sdl; + settings = sdl->common.context.settings; + + if (!settings) + return FALSE; + + if (!sdlDisp->activated || !sdlDisp->disp) + return TRUE; + + if (GetTickCount64() - sdlDisp->lastSentDate < RESIZE_MIN_DELAY) + return TRUE; + + sdlDisp->lastSentDate = GetTickCount64(); + + if (!sdl_disp_settings_changed(sdlDisp)) + return TRUE; + + /* TODO: Multimonitor support for wayland + if (sdl->fullscreen && (settings->MonitorCount > 0)) + { + if (sdl_disp_sendLayout(sdlDisp->disp, settings->MonitorDefArray, + settings->MonitorCount) != CHANNEL_RC_OK) + return FALSE; + } + else + */ + { + sdlDisp->waitingResize = TRUE; + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = sdlDisp->targetWidth; + layout.Height = sdlDisp->targetHeight; + layout.Orientation = settings->DesktopOrientation; + layout.DesktopScaleFactor = settings->DesktopScaleFactor; + layout.DeviceScaleFactor = settings->DeviceScaleFactor; + layout.PhysicalWidth = sdlDisp->targetWidth; + layout.PhysicalHeight = sdlDisp->targetHeight; + + if (IFCALLRESULT(CHANNEL_RC_OK, sdlDisp->disp->SendMonitorLayout, sdlDisp->disp, 1, + &layout) != CHANNEL_RC_OK) + return FALSE; + } + return sdl_update_last_sent(sdlDisp); +} + +static BOOL sdl_disp_set_window_resizable(sdlDispContext* sdlDisp) +{ + WINPR_ASSERT(sdlDisp); + update_resizeable(sdlDisp->sdl, TRUE); + return TRUE; +} + +static BOOL sdl_disp_check_context(void* context, sdlContext** ppsdl, sdlDispContext** ppsdlDisp, + rdpSettings** ppSettings) +{ + sdlContext* sdl; + + if (!context) + return FALSE; + + sdl = (sdlContext*)context; + + if (!(sdl->disp)) + return FALSE; + + if (!sdl->common.context.settings) + return FALSE; + + *ppsdl = sdl; + *ppsdlDisp = sdl->disp; + *ppSettings = sdl->common.context.settings; + return TRUE; +} + +static void sdl_disp_OnActivated(void* context, const ActivatedEventArgs* e) +{ + sdlContext* sdl; + sdlDispContext* sdlDisp; + rdpSettings* settings; + + if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings)) + return; + + sdlDisp->waitingResize = FALSE; + + if (sdlDisp->activated && !settings->Fullscreen) + { + sdl_disp_set_window_resizable(sdlDisp); + + if (e->firstActivation) + return; + + sdl_disp_sendResize(sdlDisp); + } +} + +static void sdl_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* e) +{ + sdlContext* sdl; + sdlDispContext* sdlDisp; + rdpSettings* settings; + + WINPR_UNUSED(e); + if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings)) + return; + + sdlDisp->waitingResize = FALSE; + + if (sdlDisp->activated && !settings->Fullscreen) + { + sdl_disp_set_window_resizable(sdlDisp); + sdl_disp_sendResize(sdlDisp); + } +} + +static void sdl_disp_OnTimer(void* context, const TimerEventArgs* e) +{ + sdlContext* sdl; + sdlDispContext* sdlDisp; + rdpSettings* settings; + + WINPR_UNUSED(e); + if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings)) + return; + + if (!sdlDisp->activated || settings->Fullscreen) + return; + + sdl_disp_sendResize(sdlDisp); +} + +sdlDispContext* sdl_disp_new(sdlContext* sdl) +{ + sdlDispContext* ret; + wPubSub* pubSub; + rdpSettings* settings; + + if (!sdl || !sdl->common.context.settings || !sdl->common.context.pubSub) + return NULL; + + settings = sdl->common.context.settings; + pubSub = sdl->common.context.pubSub; + ret = calloc(1, sizeof(sdlDispContext)); + + if (!ret) + return NULL; + + ret->sdl = sdl; + ret->lastSentWidth = ret->targetWidth = settings->DesktopWidth; + ret->lastSentHeight = ret->targetHeight = settings->DesktopHeight; + PubSub_SubscribeActivated(pubSub, sdl_disp_OnActivated); + PubSub_SubscribeGraphicsReset(pubSub, sdl_disp_OnGraphicsReset); + PubSub_SubscribeTimer(pubSub, sdl_disp_OnTimer); + return ret; +} + +void sdl_disp_free(sdlDispContext* disp) +{ + if (!disp) + return; + + if (disp->sdl) + { + wPubSub* pubSub = disp->sdl->common.context.pubSub; + PubSub_UnsubscribeActivated(pubSub, sdl_disp_OnActivated); + PubSub_UnsubscribeGraphicsReset(pubSub, sdl_disp_OnGraphicsReset); + PubSub_UnsubscribeTimer(pubSub, sdl_disp_OnTimer); + } + + free(disp); +} + +static UINT sdl_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, + size_t nmonitors) +{ + UINT ret = CHANNEL_RC_OK; + DISPLAY_CONTROL_MONITOR_LAYOUT* layouts; + size_t i; + sdlDispContext* sdlDisp; + rdpSettings* settings; + + WINPR_ASSERT(disp); + WINPR_ASSERT(monitors); + WINPR_ASSERT(nmonitors > 0); + + sdlDisp = (sdlDispContext*)disp->custom; + WINPR_ASSERT(sdlDisp); + WINPR_ASSERT(sdlDisp->sdl); + + settings = sdlDisp->sdl->common.context.settings; + WINPR_ASSERT(settings); + + layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + + if (!layouts) + return CHANNEL_RC_NO_MEMORY; + + for (i = 0; i < nmonitors; i++) + { + const rdpMonitor* monitor = &monitors[i]; + DISPLAY_CONTROL_MONITOR_LAYOUT* layout = &layouts[i]; + + layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0); + layout->Left = monitor->x; + layout->Top = monitor->y; + layout->Width = monitor->width; + layout->Height = monitor->height; + layout->Orientation = ORIENTATION_LANDSCAPE; + layout->PhysicalWidth = monitor->attributes.physicalWidth; + layout->PhysicalHeight = monitor->attributes.physicalHeight; + + switch (monitor->attributes.orientation) + { + case 90: + layout->Orientation = ORIENTATION_PORTRAIT; + break; + + case 180: + layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED; + break; + + case 270: + layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED; + break; + + case 0: + default: + /* MS-RDPEDISP - 2.2.2.2.1: + * Orientation (4 bytes): A 32-bit unsigned integer that specifies the + * orientation of the monitor in degrees. Valid values are 0, 90, 180 + * or 270 + * + * So we default to ORIENTATION_LANDSCAPE + */ + layout->Orientation = ORIENTATION_LANDSCAPE; + break; + } + + layout->DesktopScaleFactor = settings->DesktopScaleFactor; + layout->DeviceScaleFactor = settings->DeviceScaleFactor; + } + + ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts); + free(layouts); + return ret; +} + +#if SDL_VERSION_ATLEAST(2, 0, 10) +BOOL sdl_disp_handle_display_event(sdlDispContext* disp, const SDL_DisplayEvent* ev) +{ + WINPR_ASSERT(ev); + + if (!disp) + return FALSE; + sdlContext* sdl = disp->sdl; + WINPR_ASSERT(sdl); + + switch (ev->event) + { +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_DISPLAYEVENT_CONNECTED: + SDL_Log("A new display with id %d was connected", ev->display); + return TRUE; + case SDL_DISPLAYEVENT_DISCONNECTED: + SDL_Log("The display with id %d was disconnected", ev->display); + return TRUE; +#endif + case SDL_DISPLAYEVENT_ORIENTATION: + SDL_Log("The orientation of display with id %d was changed", ev->display); + return TRUE; + default: + return TRUE; + } +} +#endif + +#if !SDL_VERSION_ATLEAST(2, 0, 16) +static BOOL sdl_grab(sdlContext* sdl, Uint32 windowID, SDL_bool enable) +{ + SDL_Window* window = SDL_GetWindowFromID(windowID); + if (!window) + return FALSE; + + sdl->grab_mouse = enable; + SDL_SetWindowGrab(window, enable); + return TRUE; +} +#endif + +BOOL sdl_grab_keyboard(sdlContext* sdl, Uint32 windowID, SDL_bool enable) +{ + SDL_Window* window = SDL_GetWindowFromID(windowID); + if (!window) + return FALSE; +#if SDL_VERSION_ATLEAST(2, 0, 16) + sdl->grab_kbd = enable; + SDL_SetWindowKeyboardGrab(window, enable); + return TRUE; +#else + WLog_WARN(TAG, "Keyboard grabbing not supported by SDL2 < 2.0.16"); + return FALSE; +#endif +} + +BOOL sdl_grab_mouse(sdlContext* sdl, Uint32 windowID, SDL_bool enable) +{ + SDL_Window* window = SDL_GetWindowFromID(windowID); + if (!window) + return FALSE; +#if SDL_VERSION_ATLEAST(2, 0, 16) + sdl->grab_mouse = enable; + SDL_SetWindowMouseGrab(window, enable); + return TRUE; +#else + return sdl_grab(sdl, windowID, enable); +#endif +} + +BOOL sdl_disp_handle_window_event(sdlDispContext* disp, const SDL_WindowEvent* ev) +{ + WINPR_ASSERT(ev); + + if (!disp) + return FALSE; + sdlContext* sdl = disp->sdl; + WINPR_ASSERT(sdl); + + switch (ev->event) + { + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_MINIMIZED: + gdi_send_suppress_output(sdl->common.context.gdi, TRUE); + return TRUE; + + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + gdi_send_suppress_output(sdl->common.context.gdi, FALSE); + return TRUE; + + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + disp->targetWidth = ev->data1; + disp->targetHeight = ev->data2; + return sdl_disp_sendResize(disp); + + case SDL_WINDOWEVENT_LEAVE: + sdl_grab_keyboard(sdl, ev->windowID, SDL_FALSE); + return TRUE; + case SDL_WINDOWEVENT_ENTER: + sdl_grab_keyboard(sdl, ev->windowID, SDL_TRUE); + return sdl_keyboard_focus_in(&sdl->common.context); + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_TAKE_FOCUS: + return sdl_keyboard_focus_in(&sdl->common.context); + + default: + return TRUE; + } +} + +static UINT sdl_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB) +{ + /* we're called only if dynamic resolution update is activated */ + sdlDispContext* sdlDisp; + rdpSettings* settings; + + WINPR_ASSERT(disp); + + sdlDisp = (sdlDispContext*)disp->custom; + WINPR_ASSERT(sdlDisp); + WINPR_ASSERT(sdlDisp->sdl); + + settings = sdlDisp->sdl->common.context.settings; + WINPR_ASSERT(settings); + + WLog_DBG(TAG, + "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32 + " MaxMonitorAreaFactorB: %" PRIu32 "", + maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB); + sdlDisp->activated = TRUE; + + if (settings->Fullscreen) + return CHANNEL_RC_OK; + + WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable"); + return sdl_disp_set_window_resizable(sdlDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY; +} + +BOOL sdl_disp_init(sdlDispContext* sdlDisp, DispClientContext* disp) +{ + rdpSettings* settings; + + if (!sdlDisp || !sdlDisp->sdl || !disp) + return FALSE; + + settings = sdlDisp->sdl->common.context.settings; + + if (!settings) + return FALSE; + + sdlDisp->disp = disp; + disp->custom = (void*)sdlDisp; + + if (settings->DynamicResolutionUpdate) + { + disp->DisplayControlCaps = sdl_DisplayControlCaps; + } + + update_resizeable(sdlDisp->sdl, TRUE); + return TRUE; +} + +BOOL sdl_disp_uninit(sdlDispContext* sdlDisp, DispClientContext* disp) +{ + if (!sdlDisp || !disp) + return FALSE; + + sdlDisp->disp = NULL; + update_resizeable(sdlDisp->sdl, FALSE); + return TRUE; +} diff --git a/client/SDL/sdl_disp.h b/client/SDL/sdl_disp.h new file mode 100644 index 000000000..06e89fb85 --- /dev/null +++ b/client/SDL/sdl_disp.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Display Control Channel + * + * Copyright 2023 Armin Novak + * + * Licensed 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 FREERDP_CLIENT_SDL_DISP_H +#define FREERDP_CLIENT_SDL_DISP_H + +#include +#include + +#include "sdl_freerdp.h" + +BOOL sdl_disp_init(sdlDispContext* xfDisp, DispClientContext* disp); +BOOL sdl_disp_uninit(sdlDispContext* xfDisp, DispClientContext* disp); + +sdlDispContext* sdl_disp_new(sdlContext* sdl); +void sdl_disp_free(sdlDispContext* disp); + +#if SDL_VERSION_ATLEAST(2, 0, 10) +BOOL sdl_disp_handle_display_event(sdlDispContext* disp, const SDL_DisplayEvent* ev); +#endif + +BOOL sdl_disp_handle_window_event(sdlDispContext* disp, const SDL_WindowEvent* ev); + +BOOL sdl_grab_keyboard(sdlContext* sdl, Uint32 windowID, SDL_bool enable); +BOOL sdl_grab_mouse(sdlContext* sdl, Uint32 windowID, SDL_bool enable); + +#endif /* FREERDP_CLIENT_SDL_DISP_H */ diff --git a/client/SDL/sdl_freerdp.c b/client/SDL/sdl_freerdp.c new file mode 100644 index 000000000..0985209fa --- /dev/null +++ b/client/SDL/sdl_freerdp.c @@ -0,0 +1,1085 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL UI + * + * Copyright 2022 Armin Novak + * + * Licensed 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 + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "sdl_channels.h" +#include "sdl_freerdp.h" +#include "sdl_utils.h" +#include "sdl_disp.h" +#include "sdl_monitor.h" +#include "sdl_kbd.h" +#include "sdl_touch.h" +#include "sdl_pointer.h" + +#define TAG CLIENT_TAG("SDL") + +enum SDL_EXIT_CODE +{ + /* section 0-15: protocol-independent codes */ + SDL_EXIT_SUCCESS = 0, + SDL_EXIT_DISCONNECT = 1, + SDL_EXIT_LOGOFF = 2, + SDL_EXIT_IDLE_TIMEOUT = 3, + SDL_EXIT_LOGON_TIMEOUT = 4, + SDL_EXIT_CONN_REPLACED = 5, + SDL_EXIT_OUT_OF_MEMORY = 6, + SDL_EXIT_CONN_DENIED = 7, + SDL_EXIT_CONN_DENIED_FIPS = 8, + SDL_EXIT_USER_PRIVILEGES = 9, + SDL_EXIT_FRESH_CREDENTIALS_REQUIRED = 10, + SDL_EXIT_DISCONNECT_BY_USER = 11, + + /* section 16-31: license error set */ + SDL_EXIT_LICENSE_INTERNAL = 16, + SDL_EXIT_LICENSE_NO_LICENSE_SERVER = 17, + SDL_EXIT_LICENSE_NO_LICENSE = 18, + SDL_EXIT_LICENSE_BAD_CLIENT_MSG = 19, + SDL_EXIT_LICENSE_HWID_DOESNT_MATCH = 20, + SDL_EXIT_LICENSE_BAD_CLIENT = 21, + SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL = 22, + SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL = 23, + SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION = 24, + SDL_EXIT_LICENSE_CANT_UPGRADE = 25, + SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS = 26, + + /* section 32-127: RDP protocol error set */ + SDL_EXIT_RDP = 32, + + /* section 128-254: xfreerdp specific exit codes */ + SDL_EXIT_PARSE_ARGUMENTS = 128, + SDL_EXIT_MEMORY = 129, + SDL_EXIT_PROTOCOL = 130, + SDL_EXIT_CONN_FAILED = 131, + SDL_EXIT_AUTH_FAILURE = 132, + SDL_EXIT_NEGO_FAILURE = 133, + SDL_EXIT_LOGON_FAILURE = 134, + SDL_EXIT_ACCOUNT_LOCKED_OUT = 135, + SDL_EXIT_PRE_CONNECT_FAILED = 136, + SDL_EXIT_CONNECT_UNDEFINED = 137, + SDL_EXIT_POST_CONNECT_FAILED = 138, + SDL_EXIT_DNS_ERROR = 139, + SDL_EXIT_DNS_NAME_NOT_FOUND = 140, + SDL_EXIT_CONNECT_FAILED = 141, + SDL_EXIT_MCS_CONNECT_INITIAL_ERROR = 142, + SDL_EXIT_TLS_CONNECT_FAILED = 143, + SDL_EXIT_INSUFFICIENT_PRIVILEGES = 144, + SDL_EXIT_CONNECT_CANCELLED = 145, + + SDL_EXIT_CONNECT_TRANSPORT_FAILED = 147, + SDL_EXIT_CONNECT_PASSWORD_EXPIRED = 148, + SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149, + SDL_EXIT_CONNECT_KDC_UNREACHABLE = 150, + SDL_EXIT_CONNECT_ACCOUNT_DISABLED = 151, + SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152, + SDL_EXIT_CONNECT_CLIENT_REVOKED = 153, + SDL_EXIT_CONNECT_WRONG_PASSWORD = 154, + SDL_EXIT_CONNECT_ACCESS_DENIED = 155, + SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156, + SDL_EXIT_CONNECT_ACCOUNT_EXPIRED = 157, + SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158, + SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159, + + SDL_EXIT_UNKNOWN = 255, +}; + +struct sdl_exit_code_map_t +{ + DWORD error; + int code; + const char* code_tag; +}; + +#define ENTRY(x, y) \ + { \ + x, y, #y \ + } +static const struct sdl_exit_code_map_t sdl_exit_code_map[] = { + ENTRY(FREERDP_ERROR_SUCCESS, SDL_EXIT_SUCCESS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_DISCONNECT), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGOFF), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_IDLE_TIMEOUT), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGON_TIMEOUT), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_REPLACED), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_OUT_OF_MEMORY), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED_FIPS), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_USER_PRIVILEGES), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_FRESH_CREDENTIALS_REQUIRED), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_DISCONNECT_BY_USER), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_UNKNOWN), + + /* section 16-31: license error set */ + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_INTERNAL), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE_SERVER), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_MSG), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_HWID_DOESNT_MATCH), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE), + + /* section 32-127: RDP protocol error set */ + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_RDP), + + /* section 128-254: xfreerdp specific exit codes */ + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PARSE_ARGUMENTS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_MEMORY), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PROTOCOL), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_FAILED), + + ENTRY(FREERDP_ERROR_AUTHENTICATION_FAILED, SDL_EXIT_AUTH_FAILURE), + ENTRY(FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, SDL_EXIT_NEGO_FAILURE), + ENTRY(FREERDP_ERROR_CONNECT_LOGON_FAILURE, SDL_EXIT_LOGON_FAILURE), + ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, SDL_EXIT_ACCOUNT_LOCKED_OUT), + ENTRY(FREERDP_ERROR_PRE_CONNECT_FAILED, SDL_EXIT_PRE_CONNECT_FAILED), + ENTRY(FREERDP_ERROR_CONNECT_UNDEFINED, SDL_EXIT_CONNECT_UNDEFINED), + ENTRY(FREERDP_ERROR_POST_CONNECT_FAILED, SDL_EXIT_POST_CONNECT_FAILED), + ENTRY(FREERDP_ERROR_DNS_ERROR, SDL_EXIT_DNS_ERROR), + ENTRY(FREERDP_ERROR_DNS_NAME_NOT_FOUND, SDL_EXIT_DNS_NAME_NOT_FOUND), + ENTRY(FREERDP_ERROR_CONNECT_FAILED, SDL_EXIT_CONNECT_FAILED), + ENTRY(FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, SDL_EXIT_MCS_CONNECT_INITIAL_ERROR), + ENTRY(FREERDP_ERROR_TLS_CONNECT_FAILED, SDL_EXIT_TLS_CONNECT_FAILED), + ENTRY(FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, SDL_EXIT_INSUFFICIENT_PRIVILEGES), + ENTRY(FREERDP_ERROR_CONNECT_CANCELLED, SDL_EXIT_CONNECT_CANCELLED), + ENTRY(FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, SDL_EXIT_CONNECT_TRANSPORT_FAILED), + ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, SDL_EXIT_CONNECT_PASSWORD_EXPIRED), + ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE), + ENTRY(FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, SDL_EXIT_CONNECT_KDC_UNREACHABLE), + ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, SDL_EXIT_CONNECT_ACCOUNT_DISABLED), + ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED, + SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED), + ENTRY(FREERDP_ERROR_CONNECT_CLIENT_REVOKED, SDL_EXIT_CONNECT_CLIENT_REVOKED), + ENTRY(FREERDP_ERROR_CONNECT_WRONG_PASSWORD, SDL_EXIT_CONNECT_WRONG_PASSWORD), + ENTRY(FREERDP_ERROR_CONNECT_ACCESS_DENIED, SDL_EXIT_CONNECT_ACCESS_DENIED), + ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION), + ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, SDL_EXIT_CONNECT_ACCOUNT_EXPIRED), + ENTRY(FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED), + ENTRY(FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS, + SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS) +}; + +static const struct sdl_exit_code_map_t* sdl_map_entry_by_code(int exit_code) +{ + size_t x; + for (x = 0; x < ARRAYSIZE(sdl_exit_code_map); x++) + { + const struct sdl_exit_code_map_t* cur = &sdl_exit_code_map[x]; + if (cur->code == exit_code) + return cur; + } + return NULL; +} + +static const struct sdl_exit_code_map_t* sdl_map_entry_by_error(DWORD error) +{ + size_t x; + for (x = 0; x < ARRAYSIZE(sdl_exit_code_map); x++) + { + const struct sdl_exit_code_map_t* cur = &sdl_exit_code_map[x]; + if (cur->error == error) + return cur; + } + return NULL; +} + +static int sdl_map_error_to_exit_code(DWORD error) +{ + const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error); + if (entry) + return entry->code; + + return SDL_EXIT_CONN_FAILED; +} + +static const char* sdl_map_error_to_code_tag(DWORD error) +{ + const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error); + if (entry) + return entry->code_tag; + return NULL; +} + +static const char* sdl_map_to_code_tag(int code) +{ + const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_code(code); + if (entry) + return entry->code_tag; + return NULL; +} + +static BOOL sdl_init_sdl(sdlContext* sdl); +static DWORD WINAPI sdl_run(void* arg); +static BOOL sdl_create_windows(sdlContext* sdl); + +/* This function is called whenever a new frame starts. + * It can be used to reset invalidated areas. */ +static BOOL sdl_begin_paint(rdpContext* context) +{ + rdpGdi* gdi; + sdlContext* sdl = (sdlContext*)context; + + WINPR_ASSERT(context); + + gdi = context->gdi; + WINPR_ASSERT(gdi); + WINPR_ASSERT(gdi->primary); + WINPR_ASSERT(gdi->primary->hdc); + WINPR_ASSERT(gdi->primary->hdc->hwnd); + WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid); + gdi->primary->hdc->hwnd->invalid->null = TRUE; + gdi->primary->hdc->hwnd->ninvalid = 0; + SDL_LockSurface(sdl->primary); + return TRUE; +} + +static BOOL sdl_redraw(sdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + rdpGdi* gdi = sdl->common.context.gdi; + return gdi_send_suppress_output(gdi, FALSE); +} + +/* This function is called when the library completed composing a new + * frame. Read out the changed areas and blit them to your output device. + * The image buffer will have the format specified by gdi_init + */ +static BOOL sdl_end_paint(rdpContext* context) +{ + rdpGdi* gdi; + sdlContext* sdl = (sdlContext*)context; + + WINPR_ASSERT(context); + + gdi = context->gdi; + WINPR_ASSERT(gdi); + WINPR_ASSERT(gdi->primary); + WINPR_ASSERT(gdi->primary->hdc); + WINPR_ASSERT(gdi->primary->hdc->hwnd); + WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid); + + if (gdi->suppressOutput || gdi->primary->hdc->hwnd->invalid->null) + { + SDL_UnlockSurface(sdl->primary); + return TRUE; + } + + SDL_UnlockSurface(sdl->primary); + + const INT32 ninvalid = gdi->primary->hdc->hwnd->ninvalid; + const GDI_RGN* cinvalid = gdi->primary->hdc->hwnd->cinvalid; + + if (ninvalid < 1) + return TRUE; + + // TODO: Support multiple windows + for (size_t x = 0; x < sdl->windowCount; x++) + { + sdl_window_t* window = &sdl->windows[x]; + SDL_Surface* screen = SDL_GetWindowSurface(window->window); + + int w, h; + SDL_GetWindowSize(window->window, &w, &h); + + window->offset_x = 0; + window->offset_y = 0; + if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing)) + { + if (gdi->width < w) + { + window->offset_x = (w - gdi->width) / 2; + } + if (gdi->height < h) + { + window->offset_y = (h - gdi->height) / 2; + } + + for (INT32 i = 0; i < ninvalid; i++) + { + const GDI_RGN* rgn = &cinvalid[i]; + const SDL_Rect srcRect = { rgn->x, rgn->y, rgn->w, rgn->h }; + SDL_Rect dstRect = { window->offset_x + rgn->x, window->offset_y + rgn->y, rgn->w, + rgn->h }; + SDL_SetClipRect(sdl->primary, &srcRect); + SDL_BlitSurface(sdl->primary, &srcRect, screen, &dstRect); + } + } + else + { + const Uint32 id = SDL_GetWindowID(window->window); + for (INT32 i = 0; i < ninvalid; i++) + { + const GDI_RGN* rgn = &cinvalid[i]; + const SDL_Rect srcRect = { rgn->x, rgn->y, rgn->w, rgn->h }; + SDL_Rect dstRect = srcRect; + sdl_scale_coordinates(sdl, id, &dstRect.x, &dstRect.y, FALSE, TRUE); + sdl_scale_coordinates(sdl, id, &dstRect.w, &dstRect.h, FALSE, TRUE); + SDL_SetClipRect(sdl->primary, &srcRect); + SDL_SetClipRect(screen, &dstRect); + SDL_BlitScaled(sdl->primary, &srcRect, screen, &dstRect); + } + } + SDL_UpdateWindowSurface(window->window); + } + + return TRUE; +} + +/* Create a SDL surface from the GDI buffer */ +static BOOL sdl_create_primary(sdlContext* sdl) +{ + rdpGdi* gdi; + + WINPR_ASSERT(sdl); + + gdi = sdl->common.context.gdi; + WINPR_ASSERT(gdi); + + SDL_FreeSurface(sdl->primary); + sdl->primary = SDL_CreateRGBSurfaceWithFormatFrom( + gdi->primary_buffer, (int)gdi->width, (int)gdi->height, + (int)FreeRDPGetBitsPerPixel(gdi->dstFormat), (int)gdi->stride, sdl->sdl_pixel_format); + return sdl->primary != NULL; +} + +static BOOL sdl_desktop_resize(rdpContext* context) +{ + rdpGdi* gdi; + rdpSettings* settings; + sdlContext* sdl = (sdlContext*)context; + + WINPR_ASSERT(context); + + settings = context->settings; + WINPR_ASSERT(settings); + + gdi = context->gdi; + if (!gdi_resize(gdi, settings->DesktopWidth, settings->DesktopHeight)) + return FALSE; + return sdl_create_primary(sdl); +} + +/* This function is called to output a System BEEP */ +static BOOL sdl_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound) +{ + /* TODO: Implement */ + WINPR_UNUSED(context); + WINPR_UNUSED(play_sound); + return TRUE; +} + +/* Called before a connection is established. + * Set all configuration options to support and load channels here. */ +static BOOL sdl_pre_connect(freerdp* instance) +{ + rdpSettings* settings; + sdlContext* sdl; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + sdl = (sdlContext*)instance->context; + sdl->highDpi = TRUE; // If High DPI is available, we want unscaled data, RDP can scale itself. + + settings = instance->context->settings; + WINPR_ASSERT(settings); + + /* Optional OS identifier sent to server */ + settings->OsMajorType = OSMAJORTYPE_UNIX; + settings->OsMinorType = OSMINORTYPE_NATIVE_SDL; + /* settings->OrderSupport is initialized at this point. + * Only override it if you plan to implement custom order + * callbacks or deactiveate certain features. */ + /* Register the channel listeners. + * They are required to set up / tear down channels if they are loaded. */ + PubSub_SubscribeChannelConnected(instance->context->pubSub, sdl_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + sdl_OnChannelDisconnectedEventHandler); + + if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + UINT32 maxWidth = 0; + UINT32 maxHeight = 0; + + if (!sdl_init_sdl(sdl)) + return FALSE; + + if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight)) + return FALSE; + + if ((maxWidth != 0) && (maxHeight != 0) && + !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + { + WLog_INFO(TAG, "Update size to %ux%u", maxWidth, maxHeight); + settings->DesktopWidth = maxWidth; + settings->DesktopHeight = maxHeight; + } + } + else + { + /* Check +auth-only has a username and password. */ + if (!freerdp_settings_get_string(settings, FreeRDP_Password)) + { + WLog_INFO(TAG, "auth-only, but no password set. Please provide one."); + return FALSE; + } + + if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE)) + return FALSE; + + WLog_INFO(TAG, "Authentication only. Don't connect SDL."); + } + + /* TODO: Any code your client requires */ + return TRUE; +} + +static const char* sdl_window_get_title(rdpSettings* settings) +{ + const char* windowTitle; + UINT32 port; + BOOL addPort; + const char* name; + const char* prefix = "FreeRDP:"; + + if (!settings) + return NULL; + + windowTitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle); + if (windowTitle) + return _strdup(windowTitle); + + name = freerdp_settings_get_server_name(settings); + port = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort); + + addPort = (port != 3389); + + char buffer[MAX_PATH + 64] = { 0 }; + + if (!addPort) + sprintf_s(buffer, sizeof(buffer), "%s %s", prefix, name); + else + sprintf_s(buffer, sizeof(buffer), "%s %s:%" PRIu32, prefix, name, port); + + freerdp_settings_set_string(settings, FreeRDP_WindowTitle, buffer); + return freerdp_settings_get_string(settings, FreeRDP_WindowTitle); +} + +static void sdl_cleanup_sdl(sdlContext* sdl) +{ + const sdl_window_t empty = { 0 }; + if (!sdl) + return; + + if (sdl->thread) + { + int res = 0; + SDL_Event q = { 0 }; + q.type = SDL_QUIT; + res = SDL_PushEvent(&q); + + WaitForSingleObject(sdl->thread, INFINITE); + CloseHandle(sdl->thread); + } + for (size_t x = 0; x < sdl->windowCount; x++) + { + sdl_window_t* window = &sdl->windows[x]; + SDL_DestroyWindow(window->window); + + *window = empty; + } + SDL_FreeSurface(sdl->primary); + sdl->primary = NULL; + + sdl->windowCount = 0; + SDL_Quit(); +} + +BOOL sdl_init_sdl(sdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + SDL_Init(SDL_INIT_VIDEO); + + sdl->thread = CreateThread(NULL, 0, sdl_run, sdl, 0, NULL); + if (!sdl->thread) + goto fail; + return TRUE; +fail: + sdl_cleanup_sdl(sdl); + return FALSE; +} + +BOOL sdl_create_windows(sdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + const char* title = sdl_window_get_title(sdl->common.context.settings); + BOOL rc = FALSE; + + // TODO: Multimonitor setup + sdl->windowCount = 1; + + const UINT32 w = + freerdp_settings_get_uint32(sdl->common.context.settings, FreeRDP_DesktopWidth); + const UINT32 h = + freerdp_settings_get_uint32(sdl->common.context.settings, FreeRDP_DesktopHeight); + + sdl_window_t* window = NULL; + for (size_t x = 0; x < sdl->windowCount; x++) + { + Uint32 flags = SDL_WINDOW_SHOWN; + + if (sdl->highDpi) + { +#if SDL_VERSION_ATLEAST(2, 0, 1) + flags |= SDL_WINDOW_ALLOW_HIGHDPI; +#endif + } + + if (sdl->common.context.settings->Fullscreen || sdl->common.context.settings->UseMultimon) + flags |= SDL_WINDOW_FULLSCREEN; + + window = &sdl->windows[x]; + + window->window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + (int)w, (int)h, flags); + if (!window->window) + goto fail; + } + + rc = TRUE; +fail: + return rc; +} + +void update_resizeable(sdlContext* sdl, BOOL enable) +{ + WINPR_ASSERT(sdl); + + const rdpSettings* settings = sdl->common.context.settings; + const BOOL dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate); + const BOOL smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing); + BOOL use = (dyn && enable) || smart; + + for (uint32_t x = 0; x < sdl->windowCount; x++) + { + sdl_window_t* window = &sdl->windows[x]; + SDL_SetWindowResizable(window->window, use ? SDL_TRUE : SDL_FALSE); + } + sdl->resizeable = use; +} + +void update_fullscreen(sdlContext* sdl, BOOL enter) +{ + WINPR_ASSERT(sdl); + + for (uint32_t x = 0; x < sdl->windowCount; x++) + { + sdl_window_t* window = &sdl->windows[x]; + Uint32 curFlags = SDL_GetWindowFlags(window->window); + const BOOL isSet = (curFlags & SDL_WINDOW_FULLSCREEN); + if (enter) + curFlags |= SDL_WINDOW_FULLSCREEN; + else + curFlags &= ~SDL_WINDOW_FULLSCREEN; + + if ((enter && !isSet) || (!enter && isSet)) + SDL_SetWindowFullscreen(window->window, curFlags); + } + sdl->fullscreen = enter; +} + +static DWORD WINAPI sdl_run(void* arg) +{ + sdlContext* sdl = arg; + WINPR_ASSERT(sdl); + while (!freerdp_shall_disconnect_context(&sdl->common.context)) + { + SDL_Event windowEvent = { 0 }; + while (!freerdp_shall_disconnect_context(&sdl->common.context) && + SDL_PollEvent(&windowEvent)) + { + // SDL_Log("got event %s", sdl_event_type_str(windowEvent.type)); + switch (windowEvent.type) + { + case SDL_QUIT: + freerdp_abort_connect_context(&sdl->common.context); + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + { + const SDL_KeyboardEvent* ev = &windowEvent.key; + sdl_handle_keyboard_event(sdl, ev); + } + break; + case SDL_KEYMAPCHANGED: + { + } + break; // TODO: Switch keyboard layout + case SDL_MOUSEMOTION: + { + const SDL_MouseMotionEvent* ev = &windowEvent.motion; + sdl_handle_mouse_motion(sdl, ev); + } + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + const SDL_MouseButtonEvent* ev = &windowEvent.button; + sdl_handle_mouse_button(sdl, ev); + } + break; + case SDL_MOUSEWHEEL: + { + const SDL_MouseWheelEvent* ev = &windowEvent.wheel; + sdl_handle_mouse_wheel(sdl, ev); + } + break; + case SDL_FINGERDOWN: + { + const SDL_TouchFingerEvent* ev = &windowEvent.tfinger; + sdl_handle_touch_down(sdl, ev); + } + break; + case SDL_FINGERUP: + { + const SDL_TouchFingerEvent* ev = &windowEvent.tfinger; + sdl_handle_touch_up(sdl, ev); + } + break; + case SDL_FINGERMOTION: + { + const SDL_TouchFingerEvent* ev = &windowEvent.tfinger; + sdl_handle_touch_motion(sdl, ev); + } + break; +#if SDL_VERSION_ATLEAST(2, 0, 10) + case SDL_DISPLAYEVENT: + { + const SDL_DisplayEvent* ev = &windowEvent.display; + sdl_disp_handle_display_event(sdl->disp, ev); + } + break; +#endif + case SDL_WINDOWEVENT: + { + const SDL_WindowEvent* ev = &windowEvent.window; + sdl_disp_handle_window_event(sdl->disp, ev); + } + break; + + case SDL_RENDER_TARGETS_RESET: + sdl_redraw(sdl); + break; + case SDL_RENDER_DEVICE_RESET: + sdl_redraw(sdl); + break; + case SDL_APP_WILLENTERFOREGROUND: + sdl_redraw(sdl); + break; + default: + break; + } + } + } + return TRUE; +} + +/* Called after a RDP connection was successfully established. + * Settings might have changed during negociation of client / server feature + * support. + * + * Set up local framebuffers and paing callbacks. + * If required, register pointer callbacks to change the local mouse cursor + * when hovering over the RDP window + */ +static BOOL sdl_post_connect(freerdp* instance) +{ + sdlContext* sdl; + rdpContext* context; + + WINPR_ASSERT(instance); + + context = instance->context; + WINPR_ASSERT(context); + + sdl = (sdlContext*)context; + + if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly)) + { + /* Check +auth-only has a username and password. */ + if (!freerdp_settings_get_string(context->settings, FreeRDP_Password)) + { + WLog_INFO(TAG, "auth-only, but no password set. Please provide one."); + return FALSE; + } + + WLog_INFO(TAG, "Authentication only. Don't connect to X."); + return TRUE; + } + + if (!sdl_create_windows(sdl)) + return FALSE; + + update_resizeable(sdl, FALSE); + update_fullscreen(sdl, context->settings->Fullscreen || context->settings->UseMultimon); + + sdl->sdl_pixel_format = SDL_PIXELFORMAT_BGRA32; + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) + return FALSE; + + if (!sdl_create_primary(sdl)) + return FALSE; + + sdl->disp = sdl_disp_new(sdl); + if (!sdl->disp) + return FALSE; + + if (!sdl_register_pointer(instance->context->graphics)) + return FALSE; + + WINPR_ASSERT(context->update); + + context->update->BeginPaint = sdl_begin_paint; + context->update->EndPaint = sdl_end_paint; + context->update->PlaySound = sdl_play_sound; + context->update->DesktopResize = sdl_desktop_resize; + context->update->SetKeyboardIndicators = sdl_keyboard_set_indicators; + context->update->SetKeyboardImeStatus = sdl_keyboard_set_ime_status; + return TRUE; +} + +/* This function is called whether a session ends by failure or success. + * Clean up everything allocated by pre_connect and post_connect. + */ +static void sdl_post_disconnect(freerdp* instance) +{ + sdlContext* context; + + if (!instance) + return; + + if (!instance->context) + return; + + context = (sdlContext*)instance->context; + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + sdl_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + sdl_OnChannelDisconnectedEventHandler); + gdi_free(instance); + /* TODO : Clean up custom stuff */ + WINPR_UNUSED(context); +} + +static void sdl_post_final_disconnect(freerdp* instance) +{ + sdlContext* context; + + if (!instance) + return; + + if (!instance->context) + return; + + context = (sdlContext*)instance->context; + + sdl_disp_free(context->disp); + context->disp = NULL; + sdl_cleanup_sdl(context); +} + +/* RDP main loop. + * Connects RDP, loops while running and handles event and dispatch, cleans up + * after the connection ends. */ +static int WINAPI sdl_client_thread_proc(LPVOID arg) +{ + freerdp* instance = (freerdp*)arg; + DWORD nCount; + DWORD status; + int exit_code = SDL_EXIT_SUCCESS; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + BOOL rc = freerdp_connect(instance); + + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + if (!rc) + { + UINT32 error = freerdp_get_last_error(instance->context); + exit_code = sdl_map_error_to_exit_code(error); + } + + if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_AuthenticationOnly)) + { + DWORD code = freerdp_get_last_error(instance->context); + freerdp_abort_connect_context(instance->context); + WLog_ERR(TAG, "Authentication only, freerdp_get_last_error() %s [0x%08" PRIx32 "] %s", + freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code)); + goto disconnect; + } + + if (!rc) + { + DWORD code = freerdp_error_info(instance); + if (exit_code == SDL_EXIT_SUCCESS) + exit_code = sdl_map_error_to_exit_code(code); + + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_AUTHENTICATION_FAILED) + exit_code = SDL_EXIT_AUTH_FAILURE; + else if (code == ERRINFO_SUCCESS) + exit_code = SDL_EXIT_CONN_FAILED; + + goto disconnect; + } + + while (!freerdp_shall_disconnect_context(instance->context)) + { + /* + * win8 and server 2k12 seem to have some timing issue/race condition + * when a initial sync request is send to sync the keyboard indicators + * sending the sync event twice fixed this problem + */ + if (freerdp_focus_required(instance)) + { + if (!sdl_keyboard_focus_in(instance->context)) + goto disconnect; + if (!sdl_keyboard_focus_in(instance->context)) + goto disconnect; + } + + nCount = freerdp_get_event_handles(instance->context, handles, ARRAYSIZE(handles)); + + if (nCount == 0) + { + WLog_ERR(TAG, "%s: freerdp_get_event_handles failed", __FUNCTION__); + break; + } + + status = WaitForMultipleObjects(nCount, handles, FALSE, 100); + + if (status == WAIT_FAILED) + { + if (client_auto_reconnect(instance)) + continue; + else + { + /* + * Indicate an unsuccessful connection attempt if reconnect + * did not succeed and no other error was specified. + */ + if (freerdp_error_info(instance) == 0) + exit_code = SDL_EXIT_CONN_FAILED; + } + + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + WLog_ERR(TAG, "%s: WaitForMultipleObjects failed with %" PRIu32 "", __FUNCTION__, + status); + break; + } + + if (!freerdp_check_event_handles(instance->context)) + { + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + WLog_ERR(TAG, "Failed to check FreeRDP event handles"); + + break; + } + } + + if (exit_code == SDL_EXIT_SUCCESS) + { + DWORD code = freerdp_error_info(instance); + exit_code = sdl_map_error_to_exit_code(code); + + if ((code == SDL_EXIT_DISCONNECT) && (freerdp_get_disconnect_ultimatum(instance->context) == + Disconnect_Ultimatum_user_requested)) + { + /* This situation might be limited to Windows XP. */ + WLog_INFO(TAG, "Error info says user did not initiate but disconnect ultimatum says " + "they did; treat this as a user logoff"); + exit_code = SDL_EXIT_LOGOFF; + } + } + +disconnect: + if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_AuthenticationOnly)) + WLog_INFO(TAG, "Authentication only, exit status %s [%" PRId32 "]", + sdl_map_to_code_tag(exit_code), exit_code); + freerdp_disconnect(instance); + return exit_code; +} + +/* Optional global initializer. + * Here we just register a signal handler to print out stack traces + * if available. */ +static BOOL sdl_client_global_init(void) +{ + if (freerdp_handle_signals() != 0) + return FALSE; + + return TRUE; +} + +/* Optional global tear down */ +static void sdl_client_global_uninit(void) +{ +} + +static int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + sdlContext* tf; + const char* str_data = freerdp_get_logon_error_info_data(data); + const char* str_type = freerdp_get_logon_error_info_type(type); + + if (!instance || !instance->context) + return -1; + + tf = (sdlContext*)instance->context; + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + WINPR_UNUSED(tf); + + return 1; +} + +static BOOL sdl_client_new(freerdp* instance, rdpContext* context) +{ + sdlContext* tf = (sdlContext*)context; + + if (!instance || !context) + return FALSE; + + instance->PreConnect = sdl_pre_connect; + instance->PostConnect = sdl_post_connect; + instance->PostDisconnect = sdl_post_disconnect; + instance->PostFinalDisconnect = sdl_post_final_disconnect; + instance->AuthenticateEx = client_cli_authenticate_ex; + instance->VerifyCertificateEx = client_cli_verify_certificate_ex; + instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex; + instance->LogonErrorInfo = sdl_logon_error_info; + /* TODO: Client display set up */ + WINPR_UNUSED(tf); + return TRUE; +} + +static void sdl_client_free(freerdp* instance, rdpContext* context) +{ + sdlContext* tf = (sdlContext*)instance->context; + + if (!context) + return; + + /* TODO: Client display tear down */ + WINPR_UNUSED(tf); +} + +static int sdl_client_start(rdpContext* context) +{ + /* TODO: Start client related stuff */ + WINPR_UNUSED(context); + return 0; +} + +static int sdl_client_stop(rdpContext* context) +{ + /* TODO: Stop client related stuff */ + WINPR_UNUSED(context); + return 0; +} + +static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = sdl_client_global_init; + pEntryPoints->GlobalUninit = sdl_client_global_uninit; + pEntryPoints->ContextSize = sizeof(sdlContext); + pEntryPoints->ClientNew = sdl_client_new; + pEntryPoints->ClientFree = sdl_client_free; + pEntryPoints->ClientStart = sdl_client_start; + pEntryPoints->ClientStop = sdl_client_stop; + return 0; +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + int status; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + rdpContext* context; + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); + + if (!context) + goto fail; + + status = freerdp_client_settings_parse_command_line(context->settings, argc, argv, FALSE); + if (status) + { + rc = freerdp_client_settings_command_line_status_print(context->settings, status, argc, + argv); + if (context->settings->ListMonitors) + sdl_list_monitors((sdlContext*)context); + goto fail; + } + + if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE)) + goto fail; + + if (freerdp_client_start(context) != 0) + goto fail; + + rc = sdl_client_thread_proc(context->instance); + + if (freerdp_client_stop(context) != 0) + rc = -1; + +fail: + freerdp_client_context_free(context); + return rc; +} diff --git a/client/SDL/sdl_freerdp.h b/client/SDL/sdl_freerdp.h new file mode 100644 index 000000000..589412a28 --- /dev/null +++ b/client/SDL/sdl_freerdp.h @@ -0,0 +1,65 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed 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 FREERDP_CLIENT_SDL_H +#define FREERDP_CLIENT_SDL_H + +#include +#include +#include +#include +#include + +#include +#include + +typedef struct s_sdlDispContext sdlDispContext; +typedef struct +{ + SDL_Window* window; + int offset_x; + int offset_y; +} sdl_window_t; + +typedef struct +{ + rdpClientContext common; + + /* SDL */ + BOOL fullscreen; + BOOL resizeable; + BOOL grab_mouse; + BOOL grab_kbd; + BOOL highDpi; + + size_t windowCount; + sdl_window_t windows[16]; + + HANDLE thread; + + SDL_Surface* primary; + + sdlDispContext* disp; + Uint32 sdl_pixel_format; +} sdlContext; + +void update_resizeable(sdlContext* sdl, BOOL enable); +void update_fullscreen(sdlContext* sdl, BOOL enter); + +#endif /* FREERDP_CLIENT_SDL_H */ diff --git a/client/SDL/sdl_kbd.c b/client/SDL/sdl_kbd.c new file mode 100644 index 000000000..b1e536594 --- /dev/null +++ b/client/SDL/sdl_kbd.c @@ -0,0 +1,463 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL keyboard helper + * + * Copyright 2022 Armin Novak + * + * Licensed 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 "sdl_kbd.h" +#include "sdl_disp.h" +#include "sdl_freerdp.h" + +#include + +#include +#define TAG CLIENT_TAG("SDL.kbd") + +typedef struct +{ + Uint32 sdl; + const char* sdl_name; + UINT32 rdp; + const char* rdp_name; +} scancode_entry_t; + +#define STR(x) #x +#define ENTRY(x, y) \ + { \ + x, STR(x), y, #y \ + } +static const scancode_entry_t map[] = { + ENTRY(SDL_SCANCODE_UNKNOWN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_A, RDP_SCANCODE_KEY_A), + ENTRY(SDL_SCANCODE_B, RDP_SCANCODE_KEY_B), + ENTRY(SDL_SCANCODE_C, RDP_SCANCODE_KEY_C), + ENTRY(SDL_SCANCODE_D, RDP_SCANCODE_KEY_D), + ENTRY(SDL_SCANCODE_E, RDP_SCANCODE_KEY_E), + ENTRY(SDL_SCANCODE_F, RDP_SCANCODE_KEY_F), + ENTRY(SDL_SCANCODE_G, RDP_SCANCODE_KEY_G), + ENTRY(SDL_SCANCODE_H, RDP_SCANCODE_KEY_H), + ENTRY(SDL_SCANCODE_I, RDP_SCANCODE_KEY_I), + ENTRY(SDL_SCANCODE_J, RDP_SCANCODE_KEY_J), + ENTRY(SDL_SCANCODE_K, RDP_SCANCODE_KEY_K), + ENTRY(SDL_SCANCODE_L, RDP_SCANCODE_KEY_L), + ENTRY(SDL_SCANCODE_M, RDP_SCANCODE_KEY_M), + ENTRY(SDL_SCANCODE_N, RDP_SCANCODE_KEY_N), + ENTRY(SDL_SCANCODE_O, RDP_SCANCODE_KEY_O), + ENTRY(SDL_SCANCODE_P, RDP_SCANCODE_KEY_P), + ENTRY(SDL_SCANCODE_Q, RDP_SCANCODE_KEY_Q), + ENTRY(SDL_SCANCODE_R, RDP_SCANCODE_KEY_R), + ENTRY(SDL_SCANCODE_S, RDP_SCANCODE_KEY_S), + ENTRY(SDL_SCANCODE_T, RDP_SCANCODE_KEY_T), + ENTRY(SDL_SCANCODE_U, RDP_SCANCODE_KEY_U), + ENTRY(SDL_SCANCODE_V, RDP_SCANCODE_KEY_V), + ENTRY(SDL_SCANCODE_W, RDP_SCANCODE_KEY_W), + ENTRY(SDL_SCANCODE_X, RDP_SCANCODE_KEY_X), + ENTRY(SDL_SCANCODE_Y, RDP_SCANCODE_KEY_Y), + ENTRY(SDL_SCANCODE_Z, RDP_SCANCODE_KEY_Z), + ENTRY(SDL_SCANCODE_1, RDP_SCANCODE_KEY_1), + ENTRY(SDL_SCANCODE_2, RDP_SCANCODE_KEY_2), + ENTRY(SDL_SCANCODE_3, RDP_SCANCODE_KEY_3), + ENTRY(SDL_SCANCODE_4, RDP_SCANCODE_KEY_4), + ENTRY(SDL_SCANCODE_5, RDP_SCANCODE_KEY_5), + ENTRY(SDL_SCANCODE_6, RDP_SCANCODE_KEY_6), + ENTRY(SDL_SCANCODE_7, RDP_SCANCODE_KEY_7), + ENTRY(SDL_SCANCODE_8, RDP_SCANCODE_KEY_8), + ENTRY(SDL_SCANCODE_9, RDP_SCANCODE_KEY_9), + ENTRY(SDL_SCANCODE_0, RDP_SCANCODE_KEY_0), + ENTRY(SDL_SCANCODE_RETURN, RDP_SCANCODE_RETURN), + ENTRY(SDL_SCANCODE_ESCAPE, RDP_SCANCODE_ESCAPE), + ENTRY(SDL_SCANCODE_BACKSPACE, RDP_SCANCODE_BACKSPACE), + ENTRY(SDL_SCANCODE_TAB, RDP_SCANCODE_TAB), + ENTRY(SDL_SCANCODE_SPACE, RDP_SCANCODE_SPACE), + ENTRY(SDL_SCANCODE_MINUS, RDP_SCANCODE_OEM_MINUS), + ENTRY(SDL_SCANCODE_CAPSLOCK, RDP_SCANCODE_CAPSLOCK), + ENTRY(SDL_SCANCODE_F1, RDP_SCANCODE_F1), + ENTRY(SDL_SCANCODE_F2, RDP_SCANCODE_F2), + ENTRY(SDL_SCANCODE_F3, RDP_SCANCODE_F3), + ENTRY(SDL_SCANCODE_F4, RDP_SCANCODE_F4), + ENTRY(SDL_SCANCODE_F5, RDP_SCANCODE_F5), + ENTRY(SDL_SCANCODE_F6, RDP_SCANCODE_F6), + ENTRY(SDL_SCANCODE_F7, RDP_SCANCODE_F7), + ENTRY(SDL_SCANCODE_F8, RDP_SCANCODE_F8), + ENTRY(SDL_SCANCODE_F9, RDP_SCANCODE_F9), + ENTRY(SDL_SCANCODE_F10, RDP_SCANCODE_F10), + ENTRY(SDL_SCANCODE_F11, RDP_SCANCODE_F11), + ENTRY(SDL_SCANCODE_F12, RDP_SCANCODE_F12), + ENTRY(SDL_SCANCODE_F13, RDP_SCANCODE_F13), + ENTRY(SDL_SCANCODE_F14, RDP_SCANCODE_F14), + ENTRY(SDL_SCANCODE_F15, RDP_SCANCODE_F15), + ENTRY(SDL_SCANCODE_F16, RDP_SCANCODE_F16), + ENTRY(SDL_SCANCODE_F17, RDP_SCANCODE_F17), + ENTRY(SDL_SCANCODE_F18, RDP_SCANCODE_F18), + ENTRY(SDL_SCANCODE_F19, RDP_SCANCODE_F19), + ENTRY(SDL_SCANCODE_F20, RDP_SCANCODE_F20), + ENTRY(SDL_SCANCODE_F21, RDP_SCANCODE_F21), + ENTRY(SDL_SCANCODE_F22, RDP_SCANCODE_F22), + ENTRY(SDL_SCANCODE_F23, RDP_SCANCODE_F23), + ENTRY(SDL_SCANCODE_F24, RDP_SCANCODE_F24), + ENTRY(SDL_SCANCODE_NUMLOCKCLEAR, RDP_SCANCODE_NUMLOCK), + ENTRY(SDL_SCANCODE_KP_DIVIDE, RDP_SCANCODE_DIVIDE), + ENTRY(SDL_SCANCODE_KP_MULTIPLY, RDP_SCANCODE_MULTIPLY), + ENTRY(SDL_SCANCODE_KP_MINUS, RDP_SCANCODE_OEM_MINUS), + ENTRY(SDL_SCANCODE_KP_PLUS, RDP_SCANCODE_OEM_PLUS), + ENTRY(SDL_SCANCODE_KP_ENTER, RDP_SCANCODE_RETURN_KP), + ENTRY(SDL_SCANCODE_KP_1, RDP_SCANCODE_NUMPAD1), + ENTRY(SDL_SCANCODE_KP_2, RDP_SCANCODE_NUMPAD2), + ENTRY(SDL_SCANCODE_KP_3, RDP_SCANCODE_NUMPAD3), + ENTRY(SDL_SCANCODE_KP_4, RDP_SCANCODE_NUMPAD4), + ENTRY(SDL_SCANCODE_KP_5, RDP_SCANCODE_NUMPAD5), + ENTRY(SDL_SCANCODE_KP_6, RDP_SCANCODE_NUMPAD6), + ENTRY(SDL_SCANCODE_KP_7, RDP_SCANCODE_NUMPAD7), + ENTRY(SDL_SCANCODE_KP_8, RDP_SCANCODE_NUMPAD8), + ENTRY(SDL_SCANCODE_KP_9, RDP_SCANCODE_NUMPAD9), + ENTRY(SDL_SCANCODE_KP_0, RDP_SCANCODE_NUMPAD0), + ENTRY(SDL_SCANCODE_KP_PERIOD, RDP_SCANCODE_OEM_PERIOD), + ENTRY(SDL_SCANCODE_LCTRL, RDP_SCANCODE_LCONTROL), + ENTRY(SDL_SCANCODE_LSHIFT, RDP_SCANCODE_LSHIFT), + ENTRY(SDL_SCANCODE_LALT, RDP_SCANCODE_LMENU), + ENTRY(SDL_SCANCODE_LGUI, RDP_SCANCODE_LWIN), + ENTRY(SDL_SCANCODE_RCTRL, RDP_SCANCODE_RCONTROL), + ENTRY(SDL_SCANCODE_RSHIFT, RDP_SCANCODE_RSHIFT), + ENTRY(SDL_SCANCODE_RALT, RDP_SCANCODE_RMENU), + ENTRY(SDL_SCANCODE_RGUI, RDP_SCANCODE_RWIN), + ENTRY(SDL_SCANCODE_MODE, RDP_SCANCODE_APPS), + ENTRY(SDL_SCANCODE_MUTE, RDP_SCANCODE_VOLUME_MUTE), + ENTRY(SDL_SCANCODE_VOLUMEUP, RDP_SCANCODE_VOLUME_UP), + ENTRY(SDL_SCANCODE_VOLUMEDOWN, RDP_SCANCODE_VOLUME_DOWN), + ENTRY(SDL_SCANCODE_GRAVE, RDP_SCANCODE_OEM_3), + ENTRY(SDL_SCANCODE_COMMA, RDP_SCANCODE_OEM_COMMA), + ENTRY(SDL_SCANCODE_PERIOD, RDP_SCANCODE_OEM_PERIOD), + ENTRY(SDL_SCANCODE_SLASH, RDP_SCANCODE_OEM_2), + ENTRY(SDL_SCANCODE_BACKSLASH, RDP_SCANCODE_OEM_5), + ENTRY(SDL_SCANCODE_SCROLLLOCK, RDP_SCANCODE_SCROLLLOCK), + ENTRY(SDL_SCANCODE_INSERT, RDP_SCANCODE_INSERT), + ENTRY(SDL_SCANCODE_PRINTSCREEN, RDP_SCANCODE_PRINTSCREEN), + ENTRY(SDL_SCANCODE_HOME, RDP_SCANCODE_HOME), + ENTRY(SDL_SCANCODE_DELETE, RDP_SCANCODE_DELETE), + ENTRY(SDL_SCANCODE_RIGHT, RDP_SCANCODE_RIGHT), + ENTRY(SDL_SCANCODE_LEFT, RDP_SCANCODE_LEFT), + ENTRY(SDL_SCANCODE_DOWN, RDP_SCANCODE_DOWN), + ENTRY(SDL_SCANCODE_UP, RDP_SCANCODE_UP), + ENTRY(SDL_SCANCODE_SEMICOLON, RDP_SCANCODE_OEM_1), + ENTRY(SDL_SCANCODE_PAUSE, RDP_SCANCODE_PAUSE), + ENTRY(SDL_SCANCODE_PAGEUP, RDP_SCANCODE_PRIOR), + ENTRY(SDL_SCANCODE_END, RDP_SCANCODE_END), + ENTRY(SDL_SCANCODE_PAGEDOWN, RDP_SCANCODE_NEXT), + ENTRY(SDL_SCANCODE_AUDIONEXT, RDP_SCANCODE_MEDIA_NEXT_TRACK), + ENTRY(SDL_SCANCODE_AUDIOPREV, RDP_SCANCODE_MEDIA_PREV_TRACK), + ENTRY(SDL_SCANCODE_AUDIOSTOP, RDP_SCANCODE_MEDIA_STOP), + ENTRY(SDL_SCANCODE_AUDIOPLAY, RDP_SCANCODE_MEDIA_PLAY_PAUSE), + ENTRY(SDL_SCANCODE_AUDIOMUTE, RDP_SCANCODE_VOLUME_MUTE), + ENTRY(SDL_SCANCODE_MEDIASELECT, RDP_SCANCODE_LAUNCH_MEDIA_SELECT), + ENTRY(SDL_SCANCODE_MAIL, RDP_SCANCODE_LAUNCH_MAIL), + ENTRY(SDL_SCANCODE_APP1, RDP_SCANCODE_LAUNCH_APP1), + ENTRY(SDL_SCANCODE_APP2, RDP_SCANCODE_LAUNCH_APP2), + ENTRY(SDL_SCANCODE_SYSREQ, RDP_SCANCODE_SYSREQ), + ENTRY(SDL_SCANCODE_WWW, RDP_SCANCODE_BROWSER_HOME), + ENTRY(SDL_SCANCODE_LEFTBRACKET, RDP_SCANCODE_OEM_4), + ENTRY(SDL_SCANCODE_RIGHTBRACKET, RDP_SCANCODE_OEM_6), + ENTRY(SDL_SCANCODE_APOSTROPHE, RDP_SCANCODE_OEM_7), + ENTRY(SDL_SCANCODE_NONUSBACKSLASH, RDP_SCANCODE_OEM_102), + ENTRY(SDL_SCANCODE_SLEEP, RDP_SCANCODE_SLEEP), + ENTRY(SDL_SCANCODE_EQUALS, VK_OEM_8), + ENTRY(SDL_SCANCODE_KP_COMMA, RDP_SCANCODE_DECIMAL), + ENTRY(SDL_SCANCODE_FIND, RDP_SCANCODE_BROWSER_SEARCH), + ENTRY(SDL_SCANCODE_RETURN2, RDP_SCANCODE_RETURN_KP), + ENTRY(SDL_SCANCODE_AC_SEARCH, RDP_SCANCODE_BROWSER_SEARCH), + ENTRY(SDL_SCANCODE_AC_HOME, RDP_SCANCODE_BROWSER_HOME), + ENTRY(SDL_SCANCODE_AC_BACK, RDP_SCANCODE_BROWSER_BACK), + ENTRY(SDL_SCANCODE_AC_FORWARD, RDP_SCANCODE_BROWSER_FORWARD), + ENTRY(SDL_SCANCODE_AC_STOP, RDP_SCANCODE_BROWSER_STOP), + +#if 1 // TODO: unmapped + ENTRY(SDL_SCANCODE_NONUSHASH, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_APPLICATION, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_POWER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_EQUALS, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_EXECUTE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_HELP, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_MENU, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_SELECT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_STOP, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AGAIN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_UNDO, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CUT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_COPY, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_PASTE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_EQUALSAS400, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL1, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL2, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL3, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL4, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL5, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL6, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL7, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL8, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL9, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG1, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG2, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG3, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG4, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG5, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG6, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG7, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG8, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG9, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_ALTERASE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CANCEL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CLEAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_PRIOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_SEPARATOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_OUT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_OPER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CLEARAGAIN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CRSEL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_EXSEL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_00, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_000, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_THOUSANDSSEPARATOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_DECIMALSEPARATOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CURRENCYUNIT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CURRENCYSUBUNIT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_LEFTPAREN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_RIGHTPAREN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_LEFTBRACE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_RIGHTBRACE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_TAB, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_BACKSPACE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_A, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_B, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_C, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_D, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_E, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_F, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_XOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_POWER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_PERCENT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_LESS, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_GREATER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_AMPERSAND, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_DBLAMPERSAND, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_VERTICALBAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_DBLVERTICALBAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_COLON, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_HASH, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_SPACE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_AT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_EXCLAM, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMSTORE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMRECALL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMCLEAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMADD, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMSUBTRACT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMMULTIPLY, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMDIVIDE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_PLUSMINUS, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_CLEAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_CLEARENTRY, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_BINARY, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_OCTAL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_DECIMAL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_HEXADECIMAL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CALCULATOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_COMPUTER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AC_REFRESH, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AC_BOOKMARKS, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_BRIGHTNESSDOWN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_BRIGHTNESSUP, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_DISPLAYSWITCH, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KBDILLUMTOGGLE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KBDILLUMDOWN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KBDILLUMUP, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_EJECT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AUDIOREWIND, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AUDIOFASTFORWARD, RDP_SCANCODE_UNKNOWN) +#endif +}; + +static UINT32 sdl_get_kbd_flags(void) +{ + UINT32 flags = 0; + + SDL_Keymod mod = SDL_GetModState(); + if ((mod & KMOD_NUM) != 0) + flags |= KBD_SYNC_NUM_LOCK; + if ((mod & KMOD_CAPS) != 0) + flags |= KBD_SYNC_CAPS_LOCK; +#if SDL_VERSION_ATLEAST(2, 0, 18) + if ((mod & KMOD_SCROLL) != 0) + flags |= KBD_SYNC_SCROLL_LOCK; +#endif + + // TODO: KBD_SYNC_KANA_LOCK + + return flags; +} + +BOOL sdl_sync_kbd_state(rdpContext* context) +{ + const UINT32 syncFlags = sdl_get_kbd_flags(); + + WINPR_ASSERT(context); + return freerdp_input_send_synchronize_event(context->input, syncFlags); +} + +BOOL sdl_keyboard_focus_in(rdpContext* context) +{ + rdpInput* input; + UINT32 syncFlags; + + WINPR_ASSERT(context); + + input = context->input; + WINPR_ASSERT(input); + + syncFlags = sdl_get_kbd_flags(); + freerdp_input_send_focus_in_event(input, syncFlags); + + /* finish with a mouse pointer position like mstsc.exe if required */ +#if 0 + if (xfc->remote_app) + return; + + if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, &state)) + { + if ((x >= 0) && (x < xfc->window->width) && (y >= 0) && (y < xfc->window->height)) + { + xf_event_adjust_coordinates(xfc, &x, &y); + freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y); + } + } +#endif + return TRUE; +} + +/* This function is called to update the keyboard indocator LED */ +BOOL sdl_keyboard_set_indicators(rdpContext* context, UINT16 led_flags) +{ + WINPR_UNUSED(context); + + SDL_Keymod state = KMOD_NONE; + + if ((led_flags & KBD_SYNC_NUM_LOCK) != 0) + state |= KMOD_NUM; + if ((led_flags & KBD_SYNC_CAPS_LOCK) != 0) + state |= KMOD_CAPS; +#if SDL_VERSION_ATLEAST(2, 0, 18) + if ((led_flags & KBD_SYNC_SCROLL_LOCK) != 0) + state |= KMOD_SCROLL; +#endif + + // TODO: KBD_SYNC_KANA_LOCK + + SDL_SetModState(state); + + return TRUE; +} + +/* This function is called to set the IME state */ +BOOL sdl_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode) +{ + if (!context) + return FALSE; + + WLog_WARN(TAG, + "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32 + ", imeConvMode=%08" PRIx32 ") ignored", + imeId, imeState, imeConvMode); + return TRUE; +} + +static const char* sdl_scancode_name(Uint32 scancode) +{ + for (size_t x = 0; x < ARRAYSIZE(map); x++) + { + const scancode_entry_t* cur = &map[x]; + if (cur->sdl == scancode) + return cur->sdl_name; + } + + return "SDL_SCANCODE_UNKNOWN"; +} + +static const char* sdl_rdp_scancode_name(UINT32 scancode) +{ + for (size_t x = 0; x < ARRAYSIZE(map); x++) + { + const scancode_entry_t* cur = &map[x]; + if (cur->rdp == scancode) + return cur->rdp_name; + } + + return "RDP_SCANCODE_UNKNOWN"; +} + +static UINT32 sdl_scancode_to_rdp(Uint32 scancode) +{ + UINT32 rdp = RDP_SCANCODE_UNKNOWN; + + for (size_t x = 0; x < ARRAYSIZE(map); x++) + { + const scancode_entry_t* cur = &map[x]; + if (cur->sdl == scancode) + { + rdp = cur->rdp; + break; + } + } + + WLog_INFO(TAG, "got %s [%s] -> [%s]", SDL_GetScancodeName(scancode), + sdl_scancode_name(scancode), sdl_rdp_scancode_name(rdp)); + return rdp; +} + +BOOL sdl_handle_keyboard_event(sdlContext* sdl, const SDL_KeyboardEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + const UINT32 rdp_scancode = sdl_scancode_to_rdp(ev->keysym.scancode); + const SDL_Keymod mods = SDL_GetModState(); + const SDL_Keymod mask = KMOD_RSHIFT; + if ((mods & mask) == mask) + { + switch (ev->keysym.scancode) + { + case SDL_SCANCODE_RETURN: + if (ev->type == SDL_KEYDOWN) + { + update_fullscreen(sdl, !sdl->fullscreen); + } + return TRUE; + case SDL_SCANCODE_R: + if (ev->type == SDL_KEYDOWN) + { + update_resizeable(sdl, !sdl->resizeable); + } + return TRUE; + case SDL_SCANCODE_G: + if (ev->type == SDL_KEYDOWN) + { + sdl_grab_keyboard(sdl, ev->windowID, !sdl->grab_kbd); + } + return TRUE; + default: + break; + } + } + return freerdp_input_send_keyboard_event_ex(sdl->common.context.input, ev->type == SDL_KEYDOWN, + ev->repeat, rdp_scancode); +} diff --git a/client/SDL/sdl_kbd.h b/client/SDL/sdl_kbd.h new file mode 100644 index 000000000..761aa7346 --- /dev/null +++ b/client/SDL/sdl_kbd.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client keyboard helper + * + * Copyright 2022 Armin Novak + * + * Licensed 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 FREERDP_CLIENT_SDL_KBD_H +#define FREERDP_CLIENT_SDL_KBD_H + +#include +#include +#include + +#include "sdl_freerdp.h" + +BOOL sdl_sync_kbd_state(rdpContext* context); +BOOL sdl_keyboard_focus_in(rdpContext* context); + +BOOL sdl_keyboard_set_indicators(rdpContext* context, UINT16 led_flags); +BOOL sdl_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode); + +BOOL sdl_handle_keyboard_event(sdlContext* sdl, const SDL_KeyboardEvent* ev); + +#endif /* FREERDP_CLIENT_SDL_KBD_H */ diff --git a/client/SDL/sdl_monitor.c b/client/SDL/sdl_monitor.c new file mode 100644 index 000000000..929eba406 --- /dev/null +++ b/client/SDL/sdl_monitor.c @@ -0,0 +1,312 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Monitor Handling + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2017 David Fort + * Copyright 2018 Kai Harms + * + * Licensed 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 + +#include +#include +#include + +#include + +#include +#include + +#include + +#define TAG CLIENT_TAG("sdl") + +#include "sdl_monitor.h" + +typedef struct +{ + RECTANGLE_16 area; + RECTANGLE_16 workarea; + BOOL primary; +} MONITOR_INFO; + +typedef struct +{ + int nmonitors; + RECTANGLE_16 area; + RECTANGLE_16 workarea; + MONITOR_INFO* monitors; +} VIRTUAL_SCREEN; + +/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071 + */ + +int sdl_list_monitors(sdlContext* sdl) +{ + SDL_Init(SDL_INIT_VIDEO); + const int nmonitors = SDL_GetNumVideoDisplays(); + + for (int i = 0; i < nmonitors; i++) + { + SDL_Rect rect = { 0 }; + const int brc = SDL_GetDisplayBounds(i, &rect); + const char* name = SDL_GetDisplayName(i); + + if (brc != 0) + continue; + printf(" %s [%d] [%s] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, name, rect.w, rect.h, + rect.x, rect.y); + } + + SDL_Quit(); + return 0; +} + +static BOOL sdl_is_monitor_id_active(sdlContext* sdl, UINT32 id) +{ + UINT32 index; + const rdpSettings* settings; + + WINPR_ASSERT(sdl); + + settings = sdl->common.context.settings; + WINPR_ASSERT(settings); + + if (!settings->NumMonitorIds) + return TRUE; + + for (index = 0; index < settings->NumMonitorIds; index++) + { + if (settings->MonitorIds[index] == id) + return TRUE; + } + + return FALSE; +} + +static BOOL sdl_apply_max_size(sdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(pMaxWidth); + WINPR_ASSERT(pMaxHeight); + + rdpSettings* settings = sdl->common.context.settings; + WINPR_ASSERT(settings); + + *pMaxWidth = 0; + *pMaxHeight = 0; + + for (size_t x = 0; x < settings->MonitorCount; x++) + { + const rdpMonitor* monitor = &settings->MonitorDefArray[x]; + + if (settings->Fullscreen) + { + *pMaxWidth = monitor->width; + *pMaxHeight = monitor->height; + } + else if (settings->Workarea) + { + SDL_Rect rect = { 0 }; + SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect); + *pMaxWidth = rect.w; + *pMaxHeight = rect.h; + } + else if (settings->PercentScreen) + { + SDL_Rect rect = { 0 }; + SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect); + + *pMaxWidth = rect.w; + *pMaxHeight = rect.h; + + if (settings->PercentScreenUseWidth) + *pMaxWidth = (rect.w * settings->PercentScreen) / 100; + + if (settings->PercentScreenUseHeight) + *pMaxHeight = (rect.h * settings->PercentScreen) / 100; + } + else if (settings->DesktopWidth && settings->DesktopHeight) + { + *pMaxWidth = settings->DesktopWidth; + *pMaxHeight = settings->DesktopHeight; + } + } + return TRUE; +} + +#if SDL_VERSION_ATLEAST(2, 0, 10) +static UINT32 sdl_orientaion_to_rdp(SDL_DisplayOrientation orientation) +{ + switch (orientation) + { + case SDL_ORIENTATION_LANDSCAPE: + return ORIENTATION_LANDSCAPE; + case SDL_ORIENTATION_LANDSCAPE_FLIPPED: + return ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED; + case SDL_ORIENTATION_PORTRAIT_FLIPPED: + return ORIENTATION_PORTRAIT_FLIPPED; + case SDL_ORIENTATION_PORTRAIT: + default: + return ORIENTATION_PORTRAIT; + } +} +#endif + +static BOOL sdl_apply_display_properties(sdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + rdpSettings* settings = sdl->common.context.settings; + WINPR_ASSERT(settings); + + const UINT32 numIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds); + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorDefArray, NULL, numIds)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, numIds)) + return FALSE; + + for (UINT32 x = 0; x < numIds; x++) + { + const UINT32* id = freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x); + WINPR_ASSERT(id); + + float hdpi; + float vdpi; + SDL_Rect rect = { 0 }; + + SDL_GetDisplayBounds(*id, &rect); + SDL_GetDisplayDPI(*id, NULL, &hdpi, &vdpi); + if (sdl->highDpi) + { + // HighDPI is problematic with SDL: We can only get native resolution by creating a + // window. Work around this by checking the supported resolutions (and keep maximum) + // Also scale the DPI + const SDL_Rect scaleRect = rect; + for (int i = 0; i < SDL_GetNumDisplayModes(*id); i++) + { + SDL_DisplayMode mode = { 0 }; + SDL_GetDisplayMode(x, i, &mode); + + if (mode.w > rect.w) + { + rect.w = mode.w; + rect.h = mode.h; + } + else if (mode.w == rect.w) + { + if (mode.h > rect.h) + { + rect.w = mode.w; + rect.h = mode.h; + } + } + } + + const float dw = rect.w / scaleRect.w; + const float dh = rect.h / scaleRect.h; + hdpi /= dw; + vdpi /= dh; + } + +#if SDL_VERSION_ATLEAST(2, 0, 10) + const SDL_DisplayOrientation orientation = SDL_GetDisplayOrientation(*id); + const UINT32 rdp_orientation = sdl_orientaion_to_rdp(orientation); +#else + const UINT32 rdp_orientation = ORIENTATION_LANDSCAPE; +#endif + + rdpMonitor* monitor = + freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x); + WINPR_ASSERT(monitor); + monitor->orig_screen = x; + monitor->x = rect.x; + monitor->y = rect.y; + monitor->width = rect.w; + monitor->height = rect.h; + monitor->is_primary = (rect.x == 0) && (rect.y == 0); + monitor->attributes.desktopScaleFactor = 100; + monitor->attributes.deviceScaleFactor = 100; + monitor->attributes.orientation = rdp_orientation; + monitor->attributes.physicalWidth = rect.w / hdpi; + monitor->attributes.physicalHeight = rect.h / vdpi; + } + return TRUE; +} + +static BOOL sdl_detect_single_window(sdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(pMaxWidth); + WINPR_ASSERT(pMaxHeight); + + rdpSettings* settings = sdl->common.context.settings; + WINPR_ASSERT(settings); + + if ((!settings->UseMultimon && !settings->SpanMonitors) || + (settings->Workarea && !settings->RemoteApplicationMode)) + { + /* If no monitors were specified on the command-line then set the current monitor as active + */ + if (!settings->NumMonitorIds) + { + const size_t id = + (sdl->windowCount > 0) ? SDL_GetWindowDisplayIndex(sdl->windows[0].window) : 0; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, &id, 1)) + return FALSE; + } + else + { + + /* Always sets number of monitors from command-line to just 1. + * If the monitor is invalid then we will default back to current monitor + * later as a fallback. So, there is no need to validate command-line entry here. + */ + settings->NumMonitorIds = 1; + } + + // TODO: Fill monitor struct + if (!sdl_apply_display_properties(sdl)) + return FALSE; + return sdl_apply_max_size(sdl, pMaxWidth, pMaxHeight); + } + return TRUE; +} + +BOOL sdl_detect_monitors(sdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(pMaxWidth); + WINPR_ASSERT(pMaxHeight); + + rdpSettings* settings = sdl->common.context.settings; + WINPR_ASSERT(settings); + + const int numDisplays = SDL_GetNumVideoDisplays(); + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, NULL, numDisplays)) + return FALSE; + + for (size_t x = 0; x < numDisplays; x++) + { + if (!freerdp_settings_set_pointer_array(settings, FreeRDP_MonitorIds, x, &x)) + return FALSE; + } + + if (!sdl_apply_display_properties(sdl)) + return FALSE; + + return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight); +} diff --git a/client/SDL/sdl_monitor.h b/client/SDL/sdl_monitor.h new file mode 100644 index 000000000..ada4214bf --- /dev/null +++ b/client/SDL/sdl_monitor.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Monitor Handling + * + * Copyright 2023 Armin Novak + * + * Licensed 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 FREERDP_CLIENT_SDL_MONITOR_H +#define FREERDP_CLIENT_SDL_MONITOR_H + +#include +#include + +#include "sdl_freerdp.h" + +int sdl_list_monitors(sdlContext* sdl); +BOOL sdl_detect_monitors(sdlContext* sdl, UINT32* pWidth, UINT32* pHeight); + +#endif /* FREERDP_CLIENT_SDL_MONITOR_H */ diff --git a/client/SDL/sdl_pointer.c b/client/SDL/sdl_pointer.c new file mode 100644 index 000000000..56916f1fe --- /dev/null +++ b/client/SDL/sdl_pointer.c @@ -0,0 +1,198 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * Copyright 2023 Armin Novak + * + * Licensed 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 + +#include + +#include "sdl_pointer.h" +#include "sdl_freerdp.h" +#include "sdl_touch.h" + +#include + +#define TAG CLIENT_TAG("SDL.pointer") + +typedef struct +{ + rdpPointer pointer; + SDL_Cursor* cursor; + SDL_Surface* image; + size_t size; + void* data; +} sdlPointer; + +static BOOL sdl_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + sdlPointer* ptr = (sdlPointer*)pointer; + + WINPR_ASSERT(context); + if (!ptr) + return FALSE; + + rdpGdi* gdi = context->gdi; + WINPR_ASSERT(gdi); + + ptr->size = pointer->width * pointer->height * 4ULL; + ptr->data = winpr_aligned_malloc(ptr->size, 16); + + if (!ptr->data) + return FALSE; + + if (!freerdp_image_copy_from_pointer_data( + ptr->data, gdi->dstFormat, 0, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette)) + { + winpr_aligned_free(ptr->data); + return FALSE; + } + + return TRUE; +} + +static void sdl_Pointer_Clear(sdlPointer* ptr) +{ + WINPR_ASSERT(ptr); + SDL_FreeCursor(ptr->cursor); + SDL_FreeSurface(ptr->image); + ptr->cursor = NULL; + ptr->image = NULL; +} + +static void sdl_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + sdlPointer* ptr = (sdlPointer*)pointer; + WINPR_UNUSED(context); + + if (ptr) + { + sdl_Pointer_Clear(ptr); + winpr_aligned_free(ptr->data); + ptr->data = NULL; + } +} + +static BOOL sdl_Pointer_SetDefault(rdpContext* context) +{ + WINPR_UNUSED(context); + + SDL_Cursor* def = SDL_GetDefaultCursor(); + SDL_SetCursor(def); + SDL_ShowCursor(SDL_ENABLE); + return TRUE; +} + +static BOOL sdl_Pointer_Set(rdpContext* context, rdpPointer* pointer) +{ + sdlContext* sdl = (sdlContext*)context; + sdlPointer* ptr = (sdlPointer*)pointer; + INT32 w, h, x, y, sw, sh; + + WINPR_ASSERT(sdl); + WINPR_ASSERT(ptr); + + rdpGdi* gdi = context->gdi; + WINPR_ASSERT(gdi); + + x = (INT32)pointer->xPos; + y = (INT32)pointer->yPos; + sw = w = (INT32)pointer->width; + sh = h = (INT32)pointer->height; + + SDL_Window* window = SDL_GetMouseFocus(); + if (!window) + return sdl_Pointer_SetDefault(context); + + const Uint32 id = SDL_GetWindowID(window); + + if (!sdl_scale_coordinates(sdl, id, &x, &y, FALSE, FALSE) || + !sdl_scale_coordinates(sdl, id, &sw, &sh, FALSE, FALSE)) + return FALSE; + + sdl_Pointer_Clear(ptr); + + const DWORD bpp = FreeRDPGetBitsPerPixel(gdi->dstFormat); + ptr->image = SDL_CreateRGBSurfaceWithFormat(0, sw, sh, (int)bpp, sdl->sdl_pixel_format); + if (!ptr->image) + return FALSE; + + SDL_LockSurface(ptr->image); + const BOOL rc = + freerdp_image_scale(ptr->image->pixels, gdi->dstFormat, ptr->image->pitch, 0, 0, + ptr->image->w, ptr->image->h, ptr->data, gdi->dstFormat, 0, 0, 0, w, h); + SDL_UnlockSurface(ptr->image); + if (!rc) + return FALSE; + + ptr->cursor = SDL_CreateColorCursor(ptr->image, x, y); + if (!ptr->cursor) + return FALSE; + + SDL_SetCursor(ptr->cursor); + SDL_ShowCursor(SDL_ENABLE); + return TRUE; +} + +static BOOL sdl_Pointer_SetNull(rdpContext* context) +{ + WINPR_UNUSED(context); + + SDL_ShowCursor(SDL_DISABLE); + + return TRUE; +} + +static BOOL sdl_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + sdlContext* sdl = (sdlContext*)context; + WINPR_ASSERT(sdl); + SDL_Window* window = SDL_GetMouseFocus(); + if (!window) + return TRUE; + + const Uint32 id = SDL_GetWindowID(window); + + INT32 sx = (INT32)x; + INT32 sy = (INT32)y; + if (!sdl_scale_coordinates(sdl, id, &sx, &sy, FALSE, FALSE)) + return FALSE; + SDL_WarpMouseInWindow(window, sx, sy); + + return TRUE; +} + +BOOL sdl_register_pointer(rdpGraphics* graphics) +{ + rdpPointer* pointer = NULL; + + if (!(pointer = (rdpPointer*)calloc(1, sizeof(rdpPointer)))) + return FALSE; + + pointer->size = sizeof(sdlPointer); + pointer->New = sdl_Pointer_New; + pointer->Free = sdl_Pointer_Free; + pointer->Set = sdl_Pointer_Set; + pointer->SetNull = sdl_Pointer_SetNull; + pointer->SetDefault = sdl_Pointer_SetDefault; + pointer->SetPosition = sdl_Pointer_SetPosition; + graphics_register_pointer(graphics, pointer); + free(pointer); + return TRUE; +} diff --git a/client/SDL/sdl_pointer.h b/client/SDL/sdl_pointer.h new file mode 100644 index 000000000..7a2ce901a --- /dev/null +++ b/client/SDL/sdl_pointer.h @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Mouse Pointer + * + * Copyright 2023 Armin Novak + * + * Licensed 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 FREERDP_CLIENT_SDL_POINTER_H +#define FREERDP_CLIENT_SDL_POINTER_H + +#include + +BOOL sdl_register_pointer(rdpGraphics* graphics); + +#endif /* FREERDP_CLIENT_SDL_POINTER_H */ diff --git a/client/SDL/sdl_touch.c b/client/SDL/sdl_touch.c new file mode 100644 index 000000000..335a9ebfb --- /dev/null +++ b/client/SDL/sdl_touch.c @@ -0,0 +1,280 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL touch/mouse input + * + * Copyright 2022 Armin Novak + * + * Licensed 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 + +#include "sdl_touch.h" +#include "sdl_freerdp.h" + +#include +#include + +#include +#include + +#include + +#define TAG CLIENT_TAG("SDL.touch") + +BOOL sdl_scale_coordinates(sdlContext* sdl, Uint32 windowId, INT32* px, INT32* py, + BOOL fromLocalToRDP, BOOL applyOffset) +{ + rdpGdi* gdi; + double sx = 1.0; + double sy = 1.0; + + if (!sdl || !px || !py || !sdl->common.context.gdi) + return FALSE; + + WINPR_ASSERT(sdl->common.context.gdi); + WINPR_ASSERT(sdl->common.context.settings); + + gdi = sdl->common.context.gdi; + + // TODO: Make this multimonitor ready! + // TODO: Need to find the primary monitor, get the scale + // TODO: Need to find the destination monitor, get the scale + // TODO: All intermediate monitors, get the scale + + int offset_x = 0; + int offset_y = 0; + for (size_t x = 0; x < sdl->windowCount; x++) + { + int w, h; + const sdl_window_t* window = &sdl->windows[x]; + const Uint32 id = SDL_GetWindowID(window->window); + if (id != windowId) + { + continue; + } + SDL_GetWindowSize(window->window, &w, &h); + + sx = w / (double)gdi->width; + sy = h / (double)gdi->height; + offset_x = window->offset_x; + offset_y = window->offset_y; + break; + } + + if (sdl->common.context.settings->SmartSizing) + { + if (!fromLocalToRDP) + { + *px = (INT32)(*px * sx); + *py = (INT32)(*py * sy); + } + else + { + *px = (INT32)(*px / sx); + *py = (INT32)(*py / sy); + } + } + else if (applyOffset) + { + *px -= offset_x; + *py -= offset_y; + } + + return TRUE; +} + +static BOOL sdl_get_touch_scaled(sdlContext* sdl, const SDL_TouchFingerEvent* ev, INT32* px, + INT32* py, BOOL local) +{ + Uint32 windowID; + + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + WINPR_ASSERT(px); + WINPR_ASSERT(py); + +#if SDL_VERSION_ATLEAST(2, 0, 12) + SDL_Window* window = SDL_GetWindowFromID(ev->windowID); +#else + SDL_Window* window = SDL_GetMouseFocus(); +#endif + + if (!window) + return FALSE; + + windowID = SDL_GetWindowID(window); + SDL_Surface* surface = SDL_GetWindowSurface(window); + if (!surface) + return FALSE; + + // TODO: Add the offset of the surface in the global coordinates + *px = (INT32)(ev->x * (float)surface->w); + *py = (INT32)(ev->y * (float)surface->h); + return sdl_scale_coordinates(sdl, windowID, px, py, local, TRUE); +} + +static BOOL send_mouse_wheel(sdlContext* sdl, UINT16 flags, INT32 avalue) +{ + WINPR_ASSERT(sdl); + if (avalue < 0) + { + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + avalue = -avalue; + } + + while (avalue > 0) + { + const UINT16 cval = (avalue > 0xFF) ? 0xFF : (UINT16)avalue; + UINT16 cflags = flags | cval; + /* Convert negative values to 9bit twos complement */ + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + cflags = (flags & 0xFF00) | (0x100 - cval); + if (!freerdp_client_send_wheel_event(&sdl->common, cflags)) + return FALSE; + + avalue -= cval; + } + return TRUE; +} + +static UINT32 sdl_scale_pressure(const float pressure) +{ + const float val = pressure * 0x400; /* [MS-RDPEI] 2.2.3.3.1.1 RDPINPUT_TOUCH_CONTACT */ + if (val < 0.0f) + return 0; + if (val > 0x400) + return 0x400; + return (UINT32)val; +} + +BOOL sdl_handle_touch_up(sdlContext* sdl, const SDL_TouchFingerEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + INT32 x, y; + if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE)) + return FALSE; + return freerdp_client_handle_touch(&sdl->common, FREERDP_TOUCH_UP | FREERDP_TOUCH_HAS_PRESSURE, + (INT32)ev->fingerId, sdl_scale_pressure(ev->pressure), x, y); +} + +BOOL sdl_handle_touch_down(sdlContext* sdl, const SDL_TouchFingerEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + INT32 x, y; + if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE)) + return FALSE; + return freerdp_client_handle_touch(&sdl->common, + FREERDP_TOUCH_DOWN | FREERDP_TOUCH_HAS_PRESSURE, + (INT32)ev->fingerId, sdl_scale_pressure(ev->pressure), x, y); +} + +BOOL sdl_handle_touch_motion(sdlContext* sdl, const SDL_TouchFingerEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + INT32 x, y; + if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE)) + return FALSE; + return freerdp_client_handle_touch(&sdl->common, + FREERDP_TOUCH_MOTION | FREERDP_TOUCH_HAS_PRESSURE, + (INT32)ev->fingerId, sdl_scale_pressure(ev->pressure), x, y); +} + +BOOL sdl_handle_mouse_motion(sdlContext* sdl, const SDL_MouseMotionEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + const BOOL relative = + freerdp_settings_get_bool(sdl->common.context.settings, FreeRDP_MouseUseRelativeMove); + INT32 x = relative ? ev->xrel : ev->x; + INT32 y = relative ? ev->yrel : ev->y; + sdl_scale_coordinates(sdl, ev->windowID, &x, &y, TRUE, TRUE); + return freerdp_client_send_button_event(&sdl->common, relative, PTR_FLAGS_MOVE, x, y); +} + +BOOL sdl_handle_mouse_wheel(sdlContext* sdl, const SDL_MouseWheelEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + const BOOL flipped = (ev->direction == SDL_MOUSEWHEEL_FLIPPED); + const INT32 x = ev->x * (flipped ? -1 : 1); + const INT32 y = ev->y * (flipped ? -1 : 1); + UINT16 flags = 0; + + if (y != 0) + { + flags |= PTR_FLAGS_WHEEL; + send_mouse_wheel(sdl, flags, y); + } + + if (x != 0) + { + flags |= PTR_FLAGS_HWHEEL; + send_mouse_wheel(sdl, flags, x); + } + return TRUE; +} + +BOOL sdl_handle_mouse_button(sdlContext* sdl, const SDL_MouseButtonEvent* ev) +{ + UINT16 flags = 0; + UINT16 xflags = 0; + + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + if (ev->state == SDL_PRESSED) + { + flags |= PTR_FLAGS_DOWN; + xflags |= PTR_XFLAGS_DOWN; + } + + switch (ev->button) + { + case 1: + flags |= PTR_FLAGS_BUTTON1; + break; + case 2: + flags |= PTR_FLAGS_BUTTON3; + break; + case 3: + flags |= PTR_FLAGS_BUTTON2; + break; + case 4: + xflags |= PTR_XFLAGS_BUTTON1; + break; + case 5: + xflags |= PTR_XFLAGS_BUTTON2; + break; + default: + break; + } + + INT32 x = ev->x; + INT32 y = ev->y; + sdl_scale_coordinates(sdl, ev->windowID, &x, &y, TRUE, TRUE); + if ((flags & (~PTR_FLAGS_DOWN)) != 0) + return freerdp_client_send_button_event(&sdl->common, FALSE, flags, x, y); + else if ((xflags & (~PTR_XFLAGS_DOWN)) != 0) + return freerdp_client_send_extended_button_event(&sdl->common, FALSE, xflags, x, y); + else + return FALSE; +} diff --git a/client/SDL/sdl_touch.h b/client/SDL/sdl_touch.h new file mode 100644 index 000000000..afb1e0325 --- /dev/null +++ b/client/SDL/sdl_touch.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL touch/mouse input + * + * Copyright 2022 Armin Novak + * + * Licensed 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 FREERDP_CLIENT_SDL_TOUCH_H +#define FREERDP_CLIENT_SDL_TOUCH_H + +#include "sdl_freerdp.h" + +BOOL sdl_scale_coordinates(sdlContext* sdl, Uint32 windowId, INT32* px, INT32* py, + BOOL fromLocalToRDP, BOOL applyOffset); + +BOOL sdl_handle_mouse_motion(sdlContext* sdl, const SDL_MouseMotionEvent* ev); +BOOL sdl_handle_mouse_wheel(sdlContext* sdl, const SDL_MouseWheelEvent* ev); +BOOL sdl_handle_mouse_button(sdlContext* sdl, const SDL_MouseButtonEvent* ev); + +BOOL sdl_handle_touch_down(sdlContext* sdl, const SDL_TouchFingerEvent* ev); +BOOL sdl_handle_touch_up(sdlContext* sdl, const SDL_TouchFingerEvent* ev); +BOOL sdl_handle_touch_motion(sdlContext* sdl, const SDL_TouchFingerEvent* ev); + +#endif /* FREERDP_CLIENT_SDL_TOUCH_H */ diff --git a/client/SDL/sdl_utils.c b/client/SDL/sdl_utils.c new file mode 100644 index 000000000..f7698c390 --- /dev/null +++ b/client/SDL/sdl_utils.c @@ -0,0 +1,101 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed 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 +#include "sdl_utils.h" + +#include + +const char* sdl_event_type_str(Uint32 type) +{ +#define STR(x) #x +#define EV_CASE_STR(x) \ + case x: \ + return STR(x) + + switch (type) + { + EV_CASE_STR(SDL_FIRSTEVENT); + EV_CASE_STR(SDL_QUIT); + EV_CASE_STR(SDL_APP_TERMINATING); + EV_CASE_STR(SDL_APP_LOWMEMORY); + EV_CASE_STR(SDL_APP_WILLENTERBACKGROUND); + EV_CASE_STR(SDL_APP_DIDENTERBACKGROUND); + EV_CASE_STR(SDL_APP_WILLENTERFOREGROUND); + EV_CASE_STR(SDL_APP_DIDENTERFOREGROUND); +#if SDL_VERSION_ATLEAST(2, 0, 10) + EV_CASE_STR(SDL_DISPLAYEVENT); +#endif + EV_CASE_STR(SDL_WINDOWEVENT); + EV_CASE_STR(SDL_SYSWMEVENT); + EV_CASE_STR(SDL_KEYDOWN); + EV_CASE_STR(SDL_KEYUP); + EV_CASE_STR(SDL_TEXTEDITING); + EV_CASE_STR(SDL_TEXTINPUT); + EV_CASE_STR(SDL_KEYMAPCHANGED); + EV_CASE_STR(SDL_MOUSEMOTION); + EV_CASE_STR(SDL_MOUSEBUTTONDOWN); + EV_CASE_STR(SDL_MOUSEBUTTONUP); + EV_CASE_STR(SDL_MOUSEWHEEL); + EV_CASE_STR(SDL_JOYAXISMOTION); + EV_CASE_STR(SDL_JOYBALLMOTION); + EV_CASE_STR(SDL_JOYHATMOTION); + EV_CASE_STR(SDL_JOYBUTTONDOWN); + EV_CASE_STR(SDL_JOYBUTTONUP); + EV_CASE_STR(SDL_JOYDEVICEADDED); + EV_CASE_STR(SDL_JOYDEVICEREMOVED); + EV_CASE_STR(SDL_CONTROLLERAXISMOTION); + EV_CASE_STR(SDL_CONTROLLERBUTTONDOWN); + EV_CASE_STR(SDL_CONTROLLERBUTTONUP); + EV_CASE_STR(SDL_CONTROLLERDEVICEADDED); + EV_CASE_STR(SDL_CONTROLLERDEVICEREMOVED); + EV_CASE_STR(SDL_CONTROLLERDEVICEREMAPPED); +#if SDL_VERSION_ATLEAST(2, 0, 14) + EV_CASE_STR(SDL_LOCALECHANGED); + EV_CASE_STR(SDL_CONTROLLERTOUCHPADDOWN); + EV_CASE_STR(SDL_CONTROLLERTOUCHPADMOTION); + EV_CASE_STR(SDL_CONTROLLERTOUCHPADUP); + EV_CASE_STR(SDL_CONTROLLERSENSORUPDATE); +#endif + EV_CASE_STR(SDL_FINGERDOWN); + EV_CASE_STR(SDL_FINGERUP); + EV_CASE_STR(SDL_FINGERMOTION); + EV_CASE_STR(SDL_DOLLARGESTURE); + EV_CASE_STR(SDL_DOLLARRECORD); + EV_CASE_STR(SDL_MULTIGESTURE); + EV_CASE_STR(SDL_CLIPBOARDUPDATE); + EV_CASE_STR(SDL_DROPFILE); + EV_CASE_STR(SDL_DROPTEXT); + EV_CASE_STR(SDL_DROPBEGIN); + EV_CASE_STR(SDL_DROPCOMPLETE); + EV_CASE_STR(SDL_AUDIODEVICEADDED); + EV_CASE_STR(SDL_AUDIODEVICEREMOVED); +#if SDL_VERSION_ATLEAST(2, 0, 9) + EV_CASE_STR(SDL_SENSORUPDATE); +#endif + EV_CASE_STR(SDL_RENDER_TARGETS_RESET); + EV_CASE_STR(SDL_RENDER_DEVICE_RESET); + EV_CASE_STR(SDL_USEREVENT); + EV_CASE_STR(SDL_LASTEVENT); + default: + return "SDL_UNKNOWNEVENT"; + } +#undef EV_CASE_STR +#undef STR +} diff --git a/client/SDL/sdl_utils.h b/client/SDL/sdl_utils.h new file mode 100644 index 000000000..f3cbd68f9 --- /dev/null +++ b/client/SDL/sdl_utils.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed 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 FREERDP_CLIENT_SDL_UTILS_H +#define FREERDP_CLIENT_SDL_UTILS_H + +#include +#include + +const char* sdl_event_type_str(Uint32 type); + +#endif /* FREERDP_CLIENT_SDL_UTILS_H */ diff --git a/client/Wayland/wlf_input.c b/client/Wayland/wlf_input.c index 96432b385..c52cf7d28 100644 --- a/client/Wayland/wlf_input.c +++ b/client/Wayland/wlf_input.c @@ -410,7 +410,7 @@ BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev) if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) return FALSE; - return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_UP, ev->id,0, x, y); + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_UP, ev->id, 0, x, y); } BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev) @@ -429,7 +429,7 @@ BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev) if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) return FALSE; - return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_DOWN, ev->id, 0,x, y); + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_DOWN, ev->id, 0, x, y); } BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev) @@ -448,5 +448,5 @@ BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev) if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) return FALSE; - return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_MOTION,0, ev->id, x, y); + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_MOTION, 0, ev->id, x, y); } diff --git a/client/X11/xf_input.c b/client/X11/xf_input.c index 56af7029b..a40005f09 100644 --- a/client/X11/xf_input.c +++ b/client/X11/xf_input.c @@ -609,13 +609,13 @@ static int xf_input_touch_remote(xfContext* xfc, XIDeviceEvent* event, int evtyp switch (evtype) { case XI_TouchBegin: - freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_DOWN, touchId, x, y); + freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_DOWN, touchId, 0, x, y); break; case XI_TouchUpdate: - freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_MOTION, touchId, x, y); + freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_MOTION, touchId, 0, x, y); break; case XI_TouchEnd: - freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_UP, touchId, x, y); + freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_UP, touchId, 0, x, y); break; default: break; diff --git a/client/common/client.c b/client/common/client.c index 2ed6201bd..af3e32c9a 100644 --- a/client/common/client.c +++ b/client/common/client.c @@ -1421,9 +1421,9 @@ static BOOL freerdp_handle_touch_down(rdpClientContext* cctx, const FreeRDP_Touc flags |= PTR_FLAGS_MOVE; flags |= PTR_FLAGS_BUTTON1; - WINPR_ASSERT(contact->x <= UINT16_MAX); - WINPR_ASSERT(contact->y <= UINT16_MAX); - return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y); + WINPR_ASSERT(contact->x <= UINT16_MAX); + WINPR_ASSERT(contact->y <= UINT16_MAX); + return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y); } else { @@ -1465,9 +1465,9 @@ static BOOL freerdp_handle_touch_motion(rdpClientContext* cctx, const FreeRDP_To UINT16 flags = 0; flags |= PTR_FLAGS_MOVE; - WINPR_ASSERT(contact->x <= UINT16_MAX); - WINPR_ASSERT(contact->y <= UINT16_MAX); - return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y); + WINPR_ASSERT(contact->x <= UINT16_MAX); + WINPR_ASSERT(contact->y <= UINT16_MAX); + return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y); } else { @@ -1509,12 +1509,12 @@ static BOOL freerdp_client_touch_update(rdpClientContext* cctx, UINT32 flags, IN if (contact->id == touchId) { - *ppcontact = contact; + *ppcontact = contact; contact->flags = flags; contact->pressure = pressure; contact->x = x; - contact->y = y; + contact->y = y; return TRUE; } } diff --git a/cmake/ConfigOptions.cmake b/cmake/ConfigOptions.cmake index c47fcf4bb..bd68f98fa 100644 --- a/cmake/ConfigOptions.cmake +++ b/cmake/ConfigOptions.cmake @@ -69,6 +69,7 @@ option(WITH_SAMPLE "Build sample code" OFF) option(WITH_CLIENT_COMMON "Build client common library" ON) CMAKE_DEPENDENT_OPTION(WITH_CLIENT "Build client binaries" ON "WITH_CLIENT_COMMON" OFF) +CMAKE_DEPENDENT_OPTION(WITH_CLIENT_SDL "[experimental] Build SDL client " ON "WITH_CLIENT" OFF) option(WITH_SERVER "Build server binaries" OFF) diff --git a/include/freerdp/client.h b/include/freerdp/client.h index 15172e409..36005be7b 100644 --- a/include/freerdp/client.h +++ b/include/freerdp/client.h @@ -85,8 +85,8 @@ extern "C" { ALIGN64 INT32 id; ALIGN64 UINT32 count; - ALIGN64 INT32 x; - ALIGN64 INT32 y; + ALIGN64 INT32 x; + ALIGN64 INT32 y; ALIGN64 UINT32 flags; ALIGN64 UINT32 pressure; } FreeRDP_TouchContact; diff --git a/include/freerdp/constants.h b/include/freerdp/constants.h index 44fb1cb54..b1cb0d754 100644 --- a/include/freerdp/constants.h +++ b/include/freerdp/constants.h @@ -65,5 +65,6 @@ enum RDP_CODEC_ID #define OSMINORTYPE_WINDOWS_RT 0x0009 /* As of 2022-03-29 the following does not exist officially in [MS-RDPBCGR] */ #define OSMINORTYPE_NATIVE_WAYLAND (0xFFFF - 1) +#define OSMINORTYPE_NATIVE_SDL (0xFFFF - 2) #endif /* FREERDP_CONSTANTS_H */