Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,88 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP SDL Client
#
# Copyright 2022 Armin Novak <anovak@thincast.com>
#
# 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 "sdl2-freerdp")
find_package(SDL2 REQUIRED)
find_package(Threads REQUIRED)
add_subdirectory(dialogs)
set(SRCS
sdl_types.hpp
sdl_utils.cpp
sdl_utils.hpp
sdl_kbd.cpp
sdl_kbd.hpp
sdl_touch.cpp
sdl_touch.hpp
sdl_pointer.cpp
sdl_pointer.hpp
sdl_disp.cpp
sdl_disp.hpp
sdl_monitor.cpp
sdl_monitor.hpp
sdl_freerdp.hpp
sdl_freerdp.cpp
sdl_channels.hpp
sdl_channels.cpp
sdl_window.hpp
sdl_window.cpp
)
list(
APPEND
LIBS
winpr
freerdp
freerdp-client
Threads::Threads
sdl2_client_res
sdl2-dialogs
sdl-common-aad-view
sdl-common-prefs
)
if(NOT WITH_SDL_LINK_SHARED)
list(APPEND LIBS SDL2::SDL2-static)
set_target_properties(SDL2::SDL2-static PROPERTIES SYSTEM TRUE)
else()
list(APPEND LIBS SDL2::SDL2)
set_target_properties(SDL2::SDL2 PROPERTIES SYSTEM TRUE)
endif()
include_directories(${CMAKE_CURRENT_BINARY_DIR})
addtargetwithresourcefile(${MODULE_NAME} "${WIN32_GUI_FLAG}" "${PROJECT_VERSION}" SRCS)
target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/SDL")
get_target_property(SDL_CLIENT_BINARY_NAME ${MODULE_NAME} OUTPUT_NAME)
if(NOT WITH_CLIENT_SDL_VERSIONED)
string(REPLACE "${MODULE_NAME}" "${PROJECT_NAME}" SDL_CLIENT_BINARY_NAME "${SDL_CLIENT_BINARY_NAME}")
set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${SDL_CLIENT_BINARY_NAME})
endif()
string(TIMESTAMP SDL_CLIENT_YEAR "%Y")
set(SDL_CLIENT_UUID "com.freerdp.client.sdl2")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sdl_config.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/sdl_config.hpp @ONLY)
install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
install_freerdp_desktop("${MODULE_NAME}" "${SDL_CLIENT_UUID}")
add_subdirectory(man)

View File

@@ -0,0 +1,72 @@
set(SRCS
sdl_button.hpp
sdl_button.cpp
sdl_buttons.hpp
sdl_buttons.cpp
sdl_dialogs.cpp
sdl_dialogs.hpp
sdl_widget.hpp
sdl_widget.cpp
sdl_input.hpp
sdl_input.cpp
sdl_input_widgets.cpp
sdl_input_widgets.hpp
sdl_select.hpp
sdl_select.cpp
sdl_selectlist.hpp
sdl_selectlist.cpp
sdl_connection_dialog.cpp
sdl_connection_dialog.hpp
)
list(APPEND LIBS sdl2_client_res winpr)
if(NOT WITH_SDL_LINK_SHARED)
list(APPEND LIBS SDL2::SDL2-static)
else()
list(APPEND LIBS SDL2::SDL2)
endif()
macro(find_sdl_component name)
find_package(${name})
if(NOT ${name}_FOUND)
find_package(PkgConfig REQUIRED)
pkg_check_modules(${name} REQUIRED ${name})
if(BUILD_SHARED_LIBS)
list(APPEND LIBS ${${name}_LIBRARIES})
link_directories(${${name}_LIBRARY_DIRS})
include_directories(SYSTEM ${${name}_INCLUDE_DIRS})
else()
list(APPEND LIBS ${${name}_STATIC_LIBRARIES})
link_directories(${${name}_STATIC_LIBRARY_DIRS})
include_directories(SYSTEM ${${name}_STATIC_INCLUDE_DIRS})
endif()
else()
if(WITH_SDL_LINK_SHARED)
list(APPEND LIBS ${name}::${name})
set_target_properties(${name}::${name} PROPERTIES SYSTEM TRUE)
else()
list(APPEND LIBS ${name}::${name}-static)
set_target_properties(${name}::${name}-static PROPERTIES SYSTEM TRUE)
endif()
endif()
endmacro()
find_sdl_component(SDL2_ttf)
option(WITH_SDL_IMAGE_DIALOGS "Build with SDL_image support (recommended)" OFF)
if(WITH_SDL_IMAGE_DIALOGS)
find_sdl_component(SDL2_image)
add_compile_definitions(WITH_SDL_IMAGE_DIALOGS)
endif()
add_subdirectory(res)
add_library(sdl2-dialogs STATIC ${SRCS})
target_link_libraries(sdl2-dialogs PRIVATE ${LIBS})
if(BUILD_TESTING_INTERNAL OR BUILD_TESTING)
# add_subdirectory(test)
endif()

View File

@@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP SDL Client
#
# Copyright 2024 Armin Novak <anovak@thincast.com>
# Copyright 2024 Thincast Technologies GmbH
#
# 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(SRCS sdl2_resource_manager.cpp sdl2_resource_manager.hpp)
add_library(sdl2_client_res STATIC ${SRCS})
if(NOT WITH_SDL_LINK_SHARED)
target_link_libraries(sdl2_client_res ${SDL2_STATIC_LIBRARIES})
else()
target_link_libraries(sdl2_client_res ${SDL2_LIBRARIES})
endif()
set_target_properties(sdl2_client_res PROPERTIES POSITION_INDEPENDENT_CODE ON INTERPROCEDURAL_OPTIMIZATION OFF)
target_link_libraries(sdl2_client_res sdl-common-client-res)

View File

@@ -0,0 +1,37 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 "sdl2_resource_manager.hpp"
#include <iostream>
SDL_RWops* SDL2ResourceManager::get(const std::string& type, const std::string& id)
{
if (useCompiledResources())
{
auto d = data(type, id);
if (!d)
return nullptr;
auto s = d->size();
if (s > INT32_MAX)
return nullptr;
return SDL_RWFromConstMem(d->data(), static_cast<int>(s));
}
auto name = filename(type, id);
return SDL_RWFromFile(name.c_str(), "rb");
}

View File

@@ -0,0 +1,38 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <string>
#include <map>
#include <vector>
#include <SDL.h>
#include <res/sdl_resource_manager.hpp>
class SDL2ResourceManager : public SDLResourceManager
{
public:
SDL2ResourceManager() = delete;
SDL2ResourceManager(const SDL2ResourceManager& other) = delete;
SDL2ResourceManager(const SDL2ResourceManager&& other) = delete;
~SDL2ResourceManager() = delete;
SDL2ResourceManager& operator=(const SDL2ResourceManager& other) = delete;
SDL2ResourceManager& operator=(SDL2ResourceManager&& other) = delete;
static SDL_RWops* get(const std::string& type, const std::string& id);
};

View File

@@ -0,0 +1,71 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client Channels
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
* Copyright 2023 Thincast Technologies GmbH
*
* 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 <cassert>
#include <utility>
#include "sdl_button.hpp"
static const SDL_Color buttonbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
static const SDL_Color buttonhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
static const SDL_Color buttonmouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
static const SDL_Color buttonfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
SdlButton::SdlButton(SDL_Renderer* renderer, std::string label, int id, SDL_Rect rect)
: SdlWidget(renderer, rect, false), _name(std::move(label)), _id(id)
{
assert(renderer);
update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
}
SdlButton::SdlButton(SdlButton&& other) noexcept = default;
SdlButton::~SdlButton() = default;
bool SdlButton::highlight(SDL_Renderer* renderer)
{
assert(renderer);
std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonhighlightcolor };
if (!fill(renderer, colors))
return false;
return update_text(renderer, _name, buttonfontcolor);
}
bool SdlButton::mouseover(SDL_Renderer* renderer)
{
std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonmouseovercolor };
if (!fill(renderer, colors))
return false;
return update_text(renderer, _name, buttonfontcolor);
}
bool SdlButton::update(SDL_Renderer* renderer)
{
assert(renderer);
return update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
}
int SdlButton::id() const
{
return _id;
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <string>
#include "sdl_widget.hpp"
class SdlButton : public SdlWidget
{
public:
SdlButton(SDL_Renderer* renderer, std::string label, int id, SDL_Rect rect);
SdlButton(const SdlButton& other) = delete;
SdlButton(SdlButton&& other) noexcept;
~SdlButton() override;
SdlButton& operator=(const SdlButton& other) = delete;
SdlButton& operator=(SdlButton&& other) = delete;
bool highlight(SDL_Renderer* renderer);
bool mouseover(SDL_Renderer* renderer);
bool update(SDL_Renderer* renderer);
[[nodiscard]] int id() const;
private:
std::string _name;
int _id;
};

View File

@@ -0,0 +1,112 @@
#include <cassert>
#include <algorithm>
#include <winpr/cast.h>
#include "sdl_buttons.hpp"
static const Uint32 hpadding = 10;
SdlButtonList::~SdlButtonList() = default;
bool SdlButtonList::populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY,
Sint32 width, Sint32 height)
{
assert(renderer);
assert(width >= 0);
assert(height >= 0);
assert(labels.size() == ids.size());
_list.clear();
size_t button_width =
ids.size() * (WINPR_ASSERTING_INT_CAST(uint32_t, width) + hpadding) + hpadding;
size_t offsetX =
WINPR_ASSERTING_INT_CAST(uint32_t, total_width) -
std::min<size_t>(WINPR_ASSERTING_INT_CAST(uint32_t, total_width), button_width);
for (size_t x = 0; x < ids.size(); x++)
{
const size_t curOffsetX = offsetX + x * (static_cast<size_t>(width) + hpadding);
const SDL_Rect rect = { static_cast<int>(curOffsetX), offsetY, width, height };
_list.emplace_back(renderer, labels.at(x), ids.at(x), rect);
}
return true;
}
SdlButton* SdlButtonList::get_selected(const SDL_MouseButtonEvent& button)
{
const Sint32 x = button.x;
const Sint32 y = button.y;
return get_selected(x, y);
}
SdlButton* SdlButtonList::get_selected(Sint32 x, Sint32 y)
{
for (auto& btn : _list)
{
auto r = btn.rect();
if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
return &btn;
}
return nullptr;
}
bool SdlButtonList::set_highlight_next(bool reset)
{
if (reset)
_highlighted = nullptr;
else
{
auto next = _highlight_index++;
_highlight_index %= _list.size();
auto& element = _list.at(next);
_highlighted = &element;
}
return true;
}
bool SdlButtonList::set_highlight(size_t index)
{
if (index >= _list.size())
{
_highlighted = nullptr;
return false;
}
auto& element = _list.at(index);
_highlighted = &element;
_highlight_index = ++index % _list.size();
return true;
}
bool SdlButtonList::set_mouseover(Sint32 x, Sint32 y)
{
_mouseover = get_selected(x, y);
return _mouseover != nullptr;
}
void SdlButtonList::clear()
{
_list.clear();
_mouseover = nullptr;
_highlighted = nullptr;
_highlight_index = 0;
}
bool SdlButtonList::update(SDL_Renderer* renderer)
{
assert(renderer);
for (auto& btn : _list)
{
if (!btn.update(renderer))
return false;
}
if (_highlighted)
_highlighted->highlight(renderer);
if (_mouseover)
_mouseover->mouseover(renderer);
return true;
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <vector>
#include <cstdint>
#include "sdl_button.hpp"
class SdlButtonList
{
public:
SdlButtonList() = default;
virtual ~SdlButtonList();
bool populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY, Sint32 width,
Sint32 height);
bool update(SDL_Renderer* renderer);
SdlButton* get_selected(const SDL_MouseButtonEvent& button);
SdlButton* get_selected(Sint32 x, Sint32 y);
bool set_highlight_next(bool reset = false);
bool set_highlight(size_t index);
bool set_mouseover(Sint32 x, Sint32 y);
void clear();
SdlButtonList(const SdlButtonList& other) = delete;
SdlButtonList(SdlButtonList&& other) = delete;
SdlButtonList& operator=(const SdlButtonList& other) = delete;
SdlButtonList& operator=(SdlButtonList&& other) = delete;
private:
std::vector<SdlButton> _list;
SdlButton* _highlighted = nullptr;
size_t _highlight_index = 0;
SdlButton* _mouseover = nullptr;
};

View File

@@ -0,0 +1,539 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 <cassert>
#include <thread>
#include "sdl_connection_dialog.hpp"
#include "../sdl_utils.hpp"
#include "../sdl_freerdp.hpp"
#include "res/sdl2_resource_manager.hpp"
static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
static const SDL_Color textcolor = { 0xd1, 0xcf, 0xcd, 0xff };
static const SDL_Color infocolor = { 0x43, 0xe0, 0x0f, 0x60 };
static const SDL_Color warncolor = { 0xcd, 0xca, 0x35, 0x60 };
static const SDL_Color errorcolor = { 0xf7, 0x22, 0x30, 0x60 };
static const Uint32 vpadding = 5;
static const Uint32 hpadding = 5;
SDLConnectionDialog::SDLConnectionDialog(rdpContext* context) : _context(context)
{
SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
hide();
}
SDLConnectionDialog::~SDLConnectionDialog()
{
resetTimer();
destroyWindow();
SDL_Quit();
}
bool SDLConnectionDialog::visible() const
{
if (!_window || !_renderer)
return false;
auto flags = SDL_GetWindowFlags(_window);
return (flags & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0;
}
bool SDLConnectionDialog::setTitle(const char* fmt, ...)
{
std::scoped_lock lock(_mux);
va_list ap = {};
va_start(ap, fmt);
_title = print(fmt, ap);
va_end(ap);
return show(MSG_NONE);
}
bool SDLConnectionDialog::showInfo(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
auto rc = show(MSG_INFO, fmt, ap);
va_end(ap);
return rc;
}
bool SDLConnectionDialog::showWarn(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
auto rc = show(MSG_WARN, fmt, ap);
va_end(ap);
return rc;
}
bool SDLConnectionDialog::showError(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
auto rc = show(MSG_ERROR, fmt, ap);
va_end(ap);
if (!rc)
return rc;
return setTimer();
}
bool SDLConnectionDialog::show()
{
std::scoped_lock lock(_mux);
return show(_type_active);
}
bool SDLConnectionDialog::hide()
{
std::scoped_lock lock(_mux);
return show(MSG_DISCARD);
}
bool SDLConnectionDialog::running() const
{
std::scoped_lock lock(_mux);
return _running;
}
bool SDLConnectionDialog::update()
{
std::scoped_lock lock(_mux);
switch (_type)
{
case MSG_INFO:
case MSG_WARN:
case MSG_ERROR:
_type_active = _type;
createWindow();
break;
case MSG_DISCARD:
resetTimer();
destroyWindow();
break;
default:
if (_window)
{
SDL_SetWindowTitle(_window, _title.c_str());
}
break;
}
_type = MSG_NONE;
return true;
}
bool SDLConnectionDialog::setModal()
{
if (_window)
{
auto sdl = get_context(_context);
if (sdl->windows.empty())
return true;
auto parent = sdl->windows.begin()->second.window();
SDL_SetWindowModalFor(_window, parent);
SDL_RaiseWindow(_window);
}
return true;
}
bool SDLConnectionDialog::clearWindow(SDL_Renderer* renderer)
{
assert(renderer);
const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
backgroundcolor.b, backgroundcolor.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const int rcls = SDL_RenderClear(renderer);
return !widget_log_error(rcls, "SDL_RenderClear");
}
bool SDLConnectionDialog::update(SDL_Renderer* renderer)
{
std::scoped_lock lock(_mux);
if (!renderer)
return false;
if (!clearWindow(renderer))
return false;
for (auto& btn : _list)
{
if (!btn.widget.update_text(renderer, _msg, btn.fgcolor, btn.bgcolor))
return false;
}
if (!_buttons.update(renderer))
return false;
SDL_RenderPresent(renderer);
return true;
}
bool SDLConnectionDialog::wait(bool ignoreRdpContext)
{
while (running())
{
if (!ignoreRdpContext)
{
if (freerdp_shall_disconnect_context(_context))
return false;
}
std::this_thread::yield();
}
return true;
}
bool SDLConnectionDialog::handle(const SDL_Event& event)
{
Uint32 windowID = 0;
if (_window)
{
windowID = SDL_GetWindowID(_window);
}
switch (event.type)
{
case SDL_USEREVENT_RETRY_DIALOG:
return update();
case SDL_QUIT:
resetTimer();
destroyWindow();
return false;
case SDL_KEYDOWN:
case SDL_KEYUP:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
update(_renderer);
switch (event.key.keysym.sym)
{
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_ESCAPE:
case SDLK_KP_ENTER:
if (event.type == SDL_KEYUP)
{
freerdp_abort_connect_context(_context);
sdl_push_quit();
}
break;
case SDLK_TAB:
_buttons.set_highlight_next();
break;
default:
break;
}
return windowID == ev.windowID;
}
return false;
case SDL_MOUSEMOTION:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseMotionEvent&>(event);
_buttons.set_mouseover(event.button.x, event.button.y);
update(_renderer);
return windowID == ev.windowID;
}
return false;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
update(_renderer);
auto button = _buttons.get_selected(event.button);
if (button)
{
if (event.type == SDL_MOUSEBUTTONUP)
{
freerdp_abort_connect_context(_context);
sdl_push_quit();
}
}
return windowID == ev.windowID;
}
return false;
case SDL_MOUSEWHEEL:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
update(_renderer);
return windowID == ev.windowID;
}
return false;
case SDL_FINGERUP:
case SDL_FINGERDOWN:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
update(_renderer);
#if SDL_VERSION_ATLEAST(2, 0, 18)
return windowID == ev.windowID;
#else
return false;
#endif
}
return false;
case SDL_WINDOWEVENT:
{
auto& ev = reinterpret_cast<const SDL_WindowEvent&>(event);
switch (ev.event)
{
case SDL_WINDOWEVENT_CLOSE:
if (windowID == ev.windowID)
{
freerdp_abort_connect_context(_context);
sdl_push_quit();
}
break;
default:
update(_renderer);
setModal();
break;
}
return windowID == ev.windowID;
}
default:
return false;
}
}
bool SDLConnectionDialog::createWindow()
{
destroyWindow();
const int widget_height = 50;
const int widget_width = 600;
const int total_height = 300;
auto flags = WINPR_ASSERTING_INT_CAST(
uint32_t, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
auto rc = SDL_CreateWindowAndRenderer(widget_width, total_height, flags, &_window, &_renderer);
if (rc != 0)
{
widget_log_error(rc, "SDL_CreateWindowAndRenderer");
return false;
}
SDL_SetWindowTitle(_window, _title.c_str());
setModal();
SDL_Color res_bgcolor;
switch (_type_active)
{
case MSG_INFO:
res_bgcolor = infocolor;
break;
case MSG_WARN:
res_bgcolor = warncolor;
break;
case MSG_ERROR:
res_bgcolor = errorcolor;
break;
case MSG_DISCARD:
default:
res_bgcolor = backgroundcolor;
break;
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
std::string res_name;
switch (_type_active)
{
case MSG_INFO:
res_name = "icon_info.svg";
break;
case MSG_WARN:
res_name = "icon_warning.svg";
break;
case MSG_ERROR:
res_name = "icon_error.svg";
break;
case MSG_DISCARD:
default:
res_name = "";
break;
}
int height = (total_height - 3ul * vpadding) / 2ul;
SDL_Rect iconRect{ hpadding, vpadding, widget_width / 4ul - 2ul * hpadding, height };
widget_cfg_t icon{ textcolor,
res_bgcolor,
{ _renderer, iconRect,
SDL2ResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
_list.emplace_back(std::move(icon));
iconRect.y += height;
widget_cfg_t logo{ textcolor,
backgroundcolor,
{ _renderer, iconRect,
SDL2ResourceManager::get(SDLResourceManager::typeImages(),
"FreeRDP_Icon.svg") } };
_list.emplace_back(std::move(logo));
SDL_Rect rect = { widget_width / 4ul, vpadding, widget_width * 3ul / 4ul,
total_height - 3ul * vpadding - widget_height };
#else
SDL_Rect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
total_height - 2ul * vpadding };
#endif
widget_cfg_t w{ textcolor, backgroundcolor, { _renderer, rect, false } };
w.widget.set_wrap(true, widget_width);
_list.emplace_back(std::move(w));
rect.y += widget_height + vpadding;
const std::vector<int> buttonids = { 1 };
const std::vector<std::string> buttonlabels = { "cancel" };
_buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
total_height - widget_height - vpadding,
static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(widget_height));
_buttons.set_highlight(0);
SDL_ShowWindow(_window);
SDL_RaiseWindow(_window);
return true;
}
void SDLConnectionDialog::destroyWindow()
{
_buttons.clear();
_list.clear();
SDL_DestroyRenderer(_renderer);
SDL_DestroyWindow(_window);
_renderer = nullptr;
_window = nullptr;
}
bool SDLConnectionDialog::show(MsgType type, const char* fmt, va_list ap)
{
std::scoped_lock lock(_mux);
_msg = print(fmt, ap);
return show(type);
}
bool SDLConnectionDialog::show(MsgType type)
{
_type = type;
return sdl_push_user_event(SDL_USEREVENT_RETRY_DIALOG);
}
std::string SDLConnectionDialog::print(const char* fmt, va_list ap)
{
int size = -1;
std::string res;
do
{
res.resize(128);
if (size > 0)
res.resize(WINPR_ASSERTING_INT_CAST(size_t, size));
va_list copy = {};
va_copy(copy, ap);
WINPR_PRAGMA_DIAG_PUSH
WINPR_PRAGMA_DIAG_IGNORED_FORMAT_NONLITERAL
size = vsnprintf(res.data(), res.size(), fmt, copy);
WINPR_PRAGMA_DIAG_POP
va_end(copy);
} while ((size > 0) && (static_cast<size_t>(size) > res.size()));
return res;
}
bool SDLConnectionDialog::setTimer(Uint32 timeoutMS)
{
std::scoped_lock lock(_mux);
resetTimer();
_timer = SDL_AddTimer(timeoutMS, &SDLConnectionDialog::timeout, this);
_running = true;
return true;
}
void SDLConnectionDialog::resetTimer()
{
if (_running)
SDL_RemoveTimer(_timer);
_running = false;
}
Uint32 SDLConnectionDialog::timeout([[maybe_unused]] Uint32 intervalMS, void* pvthis)
{
auto self = static_cast<SDLConnectionDialog*>(pvthis);
self->hide();
self->_running = false;
return 0;
}
SDLConnectionDialogHider::SDLConnectionDialogHider(freerdp* instance)
: SDLConnectionDialogHider(get(instance))
{
}
SDLConnectionDialogHider::SDLConnectionDialogHider(rdpContext* context)
: SDLConnectionDialogHider(get(context))
{
}
SDLConnectionDialogHider::SDLConnectionDialogHider(SDLConnectionDialog* dialog) : _dialog(dialog)
{
if (_dialog)
{
_visible = _dialog->visible();
if (_visible)
{
_dialog->hide();
}
}
}
SDLConnectionDialogHider::~SDLConnectionDialogHider()
{
if (_dialog && _visible)
{
_dialog->show();
}
}
SDLConnectionDialog* SDLConnectionDialogHider::get(freerdp* instance)
{
if (!instance)
return nullptr;
return get(instance->context);
}
SDLConnectionDialog* SDLConnectionDialogHider::get(rdpContext* context)
{
auto sdl = get_context(context);
if (!sdl)
return nullptr;
return sdl->connection_dialog.get();
}

View File

@@ -0,0 +1,131 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <SDL.h>
#include <freerdp/freerdp.h>
#include "sdl_widget.hpp"
#include "sdl_buttons.hpp"
class SDLConnectionDialog
{
public:
explicit SDLConnectionDialog(rdpContext* context);
SDLConnectionDialog(const SDLConnectionDialog& other) = delete;
SDLConnectionDialog(const SDLConnectionDialog&& other) = delete;
virtual ~SDLConnectionDialog();
SDLConnectionDialog& operator=(const SDLConnectionDialog& other) = delete;
SDLConnectionDialog& operator=(SDLConnectionDialog&& other) = delete;
bool visible() const;
bool setTitle(const char* fmt, ...);
bool showInfo(const char* fmt, ...);
bool showWarn(const char* fmt, ...);
bool showError(const char* fmt, ...);
bool show();
bool hide();
bool running() const;
bool wait(bool ignoreRdpContextQuit = false);
bool handle(const SDL_Event& event);
private:
enum MsgType
{
MSG_NONE,
MSG_INFO,
MSG_WARN,
MSG_ERROR,
MSG_DISCARD
};
bool createWindow();
void destroyWindow();
bool update();
bool setModal();
static bool clearWindow(SDL_Renderer* renderer);
bool update(SDL_Renderer* renderer);
bool show(MsgType type, const char* fmt, va_list ap);
bool show(MsgType type);
static std::string print(const char* fmt, va_list ap);
bool setTimer(Uint32 timeoutMS = 15000);
void resetTimer();
static Uint32 timeout(Uint32 intervalMS, void* pvthis);
struct widget_cfg_t
{
SDL_Color fgcolor = {};
SDL_Color bgcolor = {};
SdlWidget widget;
};
rdpContext* _context = nullptr;
SDL_Window* _window = nullptr;
SDL_Renderer* _renderer = nullptr;
mutable std::mutex _mux;
std::string _title;
std::string _msg;
MsgType _type = MSG_NONE;
MsgType _type_active = MSG_NONE;
SDL_TimerID _timer = -1;
bool _running = false;
std::vector<widget_cfg_t> _list;
SdlButtonList _buttons;
};
class SDLConnectionDialogHider
{
public:
explicit SDLConnectionDialogHider(freerdp* instance);
explicit SDLConnectionDialogHider(rdpContext* context);
explicit SDLConnectionDialogHider(SDLConnectionDialog* dialog);
SDLConnectionDialogHider(const SDLConnectionDialogHider& other) = delete;
SDLConnectionDialogHider(SDLConnectionDialogHider&& other) = delete;
SDLConnectionDialogHider& operator=(const SDLConnectionDialogHider& other) = delete;
SDLConnectionDialogHider& operator=(SDLConnectionDialogHider&& other) = delete;
~SDLConnectionDialogHider();
private:
SDLConnectionDialog* get(freerdp* instance);
static SDLConnectionDialog* get(rdpContext* context);
SDLConnectionDialog* _dialog = nullptr;
bool _visible = false;
};

View File

@@ -0,0 +1,626 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 <vector>
#include <string>
#include <cassert>
#include <freerdp/log.h>
#include <freerdp/utils/smartcardlogon.h>
#include <SDL.h>
#include "../sdl_freerdp.hpp"
#include "sdl_dialogs.hpp"
#include "sdl_input.hpp"
#include "sdl_input_widgets.hpp"
#include "sdl_select.hpp"
#include "sdl_selectlist.hpp"
enum
{
SHOW_DIALOG_ACCEPT_REJECT = 1,
SHOW_DIALOG_TIMED_ACCEPT = 2
};
static const char* type_str_for_flags(UINT32 flags)
{
const char* type = "RDP-Server";
if (flags & VERIFY_CERT_FLAG_GATEWAY)
type = "RDP-Gateway";
if (flags & VERIFY_CERT_FLAG_REDIRECT)
type = "RDP-Redirect";
return type;
}
static BOOL sdl_wait_for_result(rdpContext* context, Uint32 type, SDL_Event* result)
{
const SDL_Event empty = {};
WINPR_ASSERT(context);
WINPR_ASSERT(result);
while (!freerdp_shall_disconnect_context(context))
{
*result = empty;
const int rc = SDL_PeepEvents(result, 1, SDL_GETEVENT, type, type);
if (rc > 0)
return TRUE;
Sleep(1);
}
return FALSE;
}
static int sdl_show_dialog(rdpContext* context, const char* title, const char* message,
Sint32 flags)
{
SDL_Event event = {};
if (!sdl_push_user_event(SDL_USEREVENT_SHOW_DIALOG, title, message, flags))
return 0;
if (!sdl_wait_for_result(context, SDL_USEREVENT_SHOW_RESULT, &event))
return 0;
return event.user.code;
}
BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
rdp_auth_reason reason)
{
SDL_Event event = {};
BOOL res = FALSE;
SDLConnectionDialogHider hider(instance);
const char* target = freerdp_settings_get_server_name(instance->context->settings);
switch (reason)
{
case AUTH_NLA:
break;
case AUTH_TLS:
case AUTH_RDP:
case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
if ((*username) && (*password))
return TRUE;
break;
case GW_AUTH_HTTP:
case GW_AUTH_RDG:
case GW_AUTH_RPC:
target =
freerdp_settings_get_string(instance->context->settings, FreeRDP_GatewayHostname);
break;
default:
break;
}
char* title = nullptr;
size_t titlesize = 0;
winpr_asprintf(&title, &titlesize, "Credentials required for %s", target);
CStringPtr scope(title, free);
char* u = nullptr;
char* d = nullptr;
char* p = nullptr;
assert(username);
assert(domain);
assert(password);
u = *username;
d = *domain;
p = *password;
if (!sdl_push_user_event(SDL_USEREVENT_AUTH_DIALOG, title, u, d, p, reason))
return res;
if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_AUTH_RESULT, &event))
return res;
auto arg = reinterpret_cast<SDL_UserAuthArg*>(event.padding);
res = arg->result > 0;
free(*username);
free(*domain);
free(*password);
*username = arg->user;
*domain = arg->domain;
*password = arg->password;
return res;
}
BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
DWORD* choice, BOOL gateway)
{
BOOL res = FALSE;
WINPR_ASSERT(instance);
WINPR_ASSERT(cert_list);
WINPR_ASSERT(choice);
SDLConnectionDialogHider hider(instance);
std::vector<std::string> strlist;
std::vector<const char*> list;
for (DWORD i = 0; i < count; i++)
{
const SmartcardCertInfo* cert = cert_list[i];
char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
char* msg = nullptr;
size_t len = 0;
winpr_asprintf(&msg, &len,
"%s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s",
container_name, reader, cert->userHint, cert->domainHint, cert->subject,
cert->issuer, cert->upn);
strlist.emplace_back(msg);
free(msg);
free(reader);
free(container_name);
auto& m = strlist.back();
list.push_back(m.c_str());
}
SDL_Event event = {};
const char* title = "Select a logon smartcard certificate";
if (gateway)
title = "Select a gateway logon smartcard certificate";
if (!sdl_push_user_event(SDL_USEREVENT_SCARD_DIALOG, title, list.data(), count))
return res;
if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_SCARD_RESULT, &event))
return res;
res = (event.user.code >= 0);
*choice = static_cast<DWORD>(event.user.code);
return res;
}
SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current,
[[maybe_unused]] void* userarg)
{
WINPR_ASSERT(instance);
WINPR_ASSERT(instance->context);
WINPR_ASSERT(what);
auto sdl = get_context(instance->context);
auto settings = instance->context->settings;
const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
std::scoped_lock lock(sdl->critical);
if (!sdl->connection_dialog)
return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay);
sdl->connection_dialog->setTitle("Retry connection to %s",
freerdp_settings_get_server_name(instance->context->settings));
if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
{
sdl->connection_dialog->showError("Unknown module %s, aborting", what);
return -1;
}
if (current == 0)
{
if (strcmp(what, "arm-transport") == 0)
sdl->connection_dialog->showWarn("[%s] Starting your VM. It may take up to 5 minutes",
what);
}
const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
if (!enabled)
{
sdl->connection_dialog->showError(
"Automatic reconnection disabled, terminating. Try to connect again later");
return -1;
}
const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
if (current >= max)
{
sdl->connection_dialog->showError(
"[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
"tech support for help if this keeps happening.",
what);
return -1;
}
sdl->connection_dialog->showInfo("[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz
"ms before next attempt",
what, current + 1, max, delay);
return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay);
}
BOOL sdl_present_gateway_message(freerdp* instance, [[maybe_unused]] UINT32 type,
BOOL isDisplayMandatory, BOOL isConsentMandatory, size_t length,
const WCHAR* wmessage)
{
if (!isDisplayMandatory)
return TRUE;
char* title = nullptr;
size_t len = 0;
winpr_asprintf(&title, &len, "[gateway]");
Sint32 flags = 0;
if (isConsentMandatory)
flags = SHOW_DIALOG_ACCEPT_REJECT;
else if (isDisplayMandatory)
flags = SHOW_DIALOG_TIMED_ACCEPT;
char* message = ConvertWCharNToUtf8Alloc(wmessage, length, nullptr);
SDLConnectionDialogHider hider(instance);
const int rc = sdl_show_dialog(instance->context, title, message, flags);
free(title);
free(message);
return rc > 0;
}
int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
{
int rc = -1;
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;
/* ignore LOGON_MSG_SESSION_CONTINUE message */
if (type == LOGON_MSG_SESSION_CONTINUE)
return 0;
SDLConnectionDialogHider hider(instance);
char* title = nullptr;
size_t tlen = 0;
winpr_asprintf(&title, &tlen, "[%s] info",
freerdp_settings_get_server_name(instance->context->settings));
char* message = nullptr;
size_t mlen = 0;
winpr_asprintf(&message, &mlen, "Logon Error Info %s [%s]", str_data, str_type);
rc = sdl_show_dialog(instance->context, title, message, SHOW_DIALOG_ACCEPT_REJECT);
free(title);
free(message);
return rc;
}
static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title,
const char* message)
{
SDLConnectionDialogHider hider(context);
if (!sdl_push_user_event(SDL_USEREVENT_CERT_DIALOG, title, message))
return 0;
SDL_Event event = {};
if (!sdl_wait_for_result(context, SDL_USEREVENT_CERT_RESULT, &event))
return 0;
return static_cast<DWORD>(event.user.code);
}
static char* sdl_pem_cert(const char* pem)
{
rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
if (!cert)
return nullptr;
char* fp = freerdp_certificate_get_fingerprint(cert);
char* start = freerdp_certificate_get_validity(cert, TRUE);
char* end = freerdp_certificate_get_validity(cert, FALSE);
freerdp_certificate_free(cert);
char* str = nullptr;
size_t slen = 0;
winpr_asprintf(&str, &slen,
"Valid from: %s\n"
"Valid to: %s\n"
"Thumbprint: %s\n",
start, end, fp);
free(fp);
free(start);
free(end);
return str;
}
DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject,
const char* issuer, const char* new_fingerprint,
const char* old_subject, const char* old_issuer,
const char* old_fingerprint, DWORD flags)
{
const char* type = type_str_for_flags(flags);
WINPR_ASSERT(instance);
WINPR_ASSERT(instance->context);
WINPR_ASSERT(instance->context->settings);
SDLConnectionDialogHider hider(instance);
/* Newer versions of FreeRDP allow exposing the whole PEM by setting
* FreeRDP_CertificateCallbackPreferPEM to TRUE
*/
char* new_fp_str = nullptr;
size_t len = 0;
if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
new_fp_str = sdl_pem_cert(new_fingerprint);
else
winpr_asprintf(&new_fp_str, &len, "Thumbprint: %s\n", new_fingerprint);
/* Newer versions of FreeRDP allow exposing the whole PEM by setting
* FreeRDP_CertificateCallbackPreferPEM to TRUE
*/
char* old_fp_str = nullptr;
size_t olen = 0;
if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
old_fp_str = sdl_pem_cert(old_fingerprint);
else
winpr_asprintf(&old_fp_str, &olen, "Thumbprint: %s\n", old_fingerprint);
const char* collission_str = "";
if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
{
collission_str =
"A matching entry with legacy SHA1 was found in local known_hosts2 store.\n"
"If you just upgraded from a FreeRDP version before 2.0 this is expected.\n"
"The hashing algorithm has been upgraded from SHA1 to SHA256.\n"
"All manually accepted certificates must be reconfirmed!\n"
"\n";
}
char* title = nullptr;
size_t tlen = 0;
winpr_asprintf(&title, &tlen, "Certificate for %s:%" PRIu16 " (%s) has changed", host, port,
type);
char* message = nullptr;
size_t mlen = 0;
winpr_asprintf(&message, &mlen,
"New Certificate details:\n"
"Common Name: %s\n"
"Subject: %s\n"
"Issuer: %s\n"
"%s\n"
"Old Certificate details:\n"
"Subject: %s\n"
"Issuer: %s\n"
"%s\n"
"%s\n"
"The above X.509 certificate does not match the certificate used for previous "
"connections.\n"
"This may indicate that the certificate has been tampered with.\n"
"Please contact the administrator of the RDP server and clarify.\n",
common_name, subject, issuer, new_fp_str, old_subject, old_issuer, old_fp_str,
collission_str);
const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
free(title);
free(message);
free(new_fp_str);
free(old_fp_str);
return rc;
}
DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject, const char* issuer,
const char* fingerprint, DWORD flags)
{
const char* type = type_str_for_flags(flags);
/* Newer versions of FreeRDP allow exposing the whole PEM by setting
* FreeRDP_CertificateCallbackPreferPEM to TRUE
*/
char* fp_str = nullptr;
size_t len = 0;
if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
fp_str = sdl_pem_cert(fingerprint);
else
winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint);
char* title = nullptr;
size_t tlen = 0;
winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type);
char* message = nullptr;
size_t mlen = 0;
winpr_asprintf(
&message, &mlen,
"Common Name: %s\n"
"Subject: %s\n"
"Issuer: %s\n"
"%s\n"
"The above X.509 certificate could not be verified, possibly because you do not have\n"
"the CA certificate in your certificate store, or the certificate has expired.\n"
"Please look at the OpenSSL documentation on how to add a private CA to the store.\n",
common_name, subject, issuer, fp_str);
SDLConnectionDialogHider hider(instance);
const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
free(fp_str);
free(title);
free(message);
return rc;
}
BOOL sdl_cert_dialog_show(const char* title, const char* message)
{
int buttonid = -1;
enum
{
BUTTONID_CERT_ACCEPT_PERMANENT = 23,
BUTTONID_CERT_ACCEPT_TEMPORARY = 24,
BUTTONID_CERT_DENY = 25
};
const SDL_MessageBoxButtonData buttons[] = {
{ 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" },
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" }
};
const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message,
ARRAYSIZE(buttons), buttons, nullptr };
const int rc = SDL_ShowMessageBox(&data, &buttonid);
Sint32 value = -1;
if (rc < 0)
value = 0;
else
{
switch (buttonid)
{
case BUTTONID_CERT_ACCEPT_PERMANENT:
value = 1;
break;
case BUTTONID_CERT_ACCEPT_TEMPORARY:
value = 2;
break;
default:
value = 0;
break;
}
}
return sdl_push_user_event(SDL_USEREVENT_CERT_RESULT, value);
}
BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags)
{
int buttonid = -1;
enum
{
BUTTONID_SHOW_ACCEPT = 24,
BUTTONID_SHOW_DENY = 25
};
const SDL_MessageBoxButtonData buttons[] = {
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" }
};
const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1;
const SDL_MessageBoxData data = {
SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr
};
const int rc = SDL_ShowMessageBox(&data, &buttonid);
Sint32 value = -1;
if (rc < 0)
value = 0;
else
{
switch (buttonid)
{
case BUTTONID_SHOW_ACCEPT:
value = 1;
break;
default:
value = 0;
break;
}
}
return sdl_push_user_event(SDL_USEREVENT_SHOW_RESULT, value);
}
BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args)
{
const std::vector<std::string> auth = { "Username: ", "Domain: ",
"Password: " };
const std::vector<std::string> authPin = { "Device: ", "PIN: " };
const std::vector<std::string> gw = { "GatewayUsername: ", "GatewayDomain: ",
"GatewayPassword: " };
std::vector<std::string> prompt;
Sint32 rc = -1;
switch (args->result)
{
case AUTH_SMARTCARD_PIN:
prompt = authPin;
break;
case AUTH_TLS:
case AUTH_RDP:
case AUTH_NLA:
prompt = auth;
break;
case GW_AUTH_HTTP:
case GW_AUTH_RDG:
case GW_AUTH_RPC:
prompt = gw;
break;
default:
break;
}
std::vector<std::string> result;
if (!prompt.empty())
{
std::vector<std::string> initial{ args->user ? args->user : "Smartcard", "" };
std::vector<Uint32> flags = { SdlInputWidget::SDL_INPUT_READONLY,
SdlInputWidget::SDL_INPUT_MASK };
if (args->result != AUTH_SMARTCARD_PIN)
{
initial = { args->user ? args->user : "", args->domain ? args->domain : "",
args->password ? args->password : "" };
flags = { 0, 0, SdlInputWidget::SDL_INPUT_MASK };
}
SdlInputWidgetList ilist(args->title, prompt, initial, flags);
rc = ilist.run(result);
}
if ((result.size() < prompt.size()))
rc = -1;
char* user = nullptr;
char* domain = nullptr;
char* pwd = nullptr;
if (rc > 0)
{
user = _strdup(result.at(0).c_str());
if (args->result == AUTH_SMARTCARD_PIN)
pwd = _strdup(result.at(1).c_str());
else
{
domain = _strdup(result.at(1).c_str());
pwd = _strdup(result.at(2).c_str());
}
}
return sdl_push_user_event(SDL_USEREVENT_AUTH_RESULT, user, domain, pwd, rc);
}
BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
{
const auto scount = WINPR_ASSERTING_INT_CAST(size_t, count);
std::vector<std::string> vlist;
vlist.reserve(scount);
for (size_t x = 0; x < scount; x++)
vlist.emplace_back(list[x]);
SdlSelectList slist(title, vlist);
Sint32 value = slist.run();
return sdl_push_user_event(SDL_USEREVENT_SCARD_RESULT, value);
}

View File

@@ -0,0 +1,53 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <winpr/wtypes.h>
#include <freerdp/freerdp.h>
#include "../sdl_types.hpp"
#include "../sdl_utils.hpp"
BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
rdp_auth_reason reason);
BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
DWORD* choice, BOOL gateway);
SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg);
DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject, const char* issuer,
const char* fingerprint, DWORD flags);
DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject,
const char* issuer, const char* new_fingerprint,
const char* old_subject, const char* old_issuer,
const char* old_fingerprint, DWORD flags);
int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
BOOL isConsentMandatory, size_t length, const WCHAR* message);
BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags);
BOOL sdl_cert_dialog_show(const char* title, const char* message);
BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list);
BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args);

View File

@@ -0,0 +1,177 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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_input.hpp"
#include <cassert>
#include <string>
#include <utility>
#include <SDL.h>
#include <SDL_ttf.h>
#include "sdl_widget.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
static const SDL_Color inputbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
static const SDL_Color inputhighlightcolor = { 0x80, 0, 0, 0x60 };
static const SDL_Color inputmouseovercolor = { 0, 0x80, 0, 0x60 };
static const SDL_Color inputfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
static const SDL_Color labelbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
static const Uint32 vpadding = 5;
static const Uint32 hpadding = 10;
SdlInputWidget::SdlInputWidget(SDL_Renderer* renderer, std::string label, std::string initial,
Uint32 flags, size_t offset, size_t width, size_t height)
: _flags(flags), _text(std::move(initial)), _text_label(std::move(label)),
_label(renderer,
{ 0, static_cast<int>(offset * (height + vpadding)), static_cast<int>(width),
static_cast<int>(height) },
false),
_input(renderer,
{ static_cast<int>(width + hpadding), static_cast<int>(offset * (height + vpadding)),
static_cast<int>(width), static_cast<int>(height) },
true),
_highlight(false), _mouseover(false)
{
}
SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept
: _flags(other._flags), _text(std::move(other._text)),
_text_label(std::move(other._text_label)), _label(std::move(other._label)),
_input(std::move(other._input)), _highlight(other._highlight), _mouseover(other._mouseover)
{
}
bool SdlInputWidget::fill_label(SDL_Renderer* renderer, SDL_Color color)
{
if (!_label.fill(renderer, color))
return false;
return _label.update_text(renderer, _text_label, labelfontcolor);
}
bool SdlInputWidget::update_label(SDL_Renderer* renderer)
{
return _label.update_text(renderer, _text_label, labelfontcolor, labelbackgroundcolor);
}
bool SdlInputWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
{
if (readonly())
return true;
_mouseover = mouseOver;
return update_input(renderer);
}
bool SdlInputWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
{
if (readonly())
return true;
_highlight = highlight;
return update_input(renderer);
}
bool SdlInputWidget::update_input(SDL_Renderer* renderer)
{
std::vector<SDL_Color> colors = { inputbackgroundcolor };
if (_highlight)
colors.push_back(inputhighlightcolor);
if (_mouseover)
colors.push_back(inputmouseovercolor);
if (!_input.fill(renderer, colors))
return false;
return update_input(renderer, inputfontcolor);
}
bool SdlInputWidget::resize_input(size_t size)
{
_text.resize(size);
return true;
}
bool SdlInputWidget::set_str(SDL_Renderer* renderer, const std::string& text)
{
if (readonly())
return true;
_text = text;
if (!resize_input(_text.size()))
return false;
return update_input(renderer);
}
bool SdlInputWidget::remove_str(SDL_Renderer* renderer, size_t count)
{
if (readonly())
return true;
assert(renderer);
if (_text.empty())
return true;
if (!resize_input(_text.size() - count))
return false;
return update_input(renderer);
}
bool SdlInputWidget::append_str(SDL_Renderer* renderer, const std::string& text)
{
assert(renderer);
if (readonly())
return true;
_text.append(text);
if (!resize_input(_text.size()))
return false;
return update_input(renderer);
}
const SDL_Rect& SdlInputWidget::input_rect() const
{
return _input.rect();
}
std::string SdlInputWidget::value() const
{
return _text;
}
bool SdlInputWidget::readonly() const
{
return (_flags & SDL_INPUT_READONLY) != 0;
}
bool SdlInputWidget::update_input(SDL_Renderer* renderer, SDL_Color fgcolor)
{
std::string text = _text;
if (!text.empty())
{
if (_flags & SDL_INPUT_MASK)
{
for (char& x : text)
x = '*';
}
}
return _input.update_text(renderer, text, fgcolor);
}

View File

@@ -0,0 +1,74 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <vector>
#include <string>
#include <SDL.h>
#include "sdl_widget.hpp"
class SdlInputWidget
{
public:
enum
{
SDL_INPUT_MASK = 1,
SDL_INPUT_READONLY = 2
};
SdlInputWidget(SDL_Renderer* renderer, std::string label, std::string initial, Uint32 flags,
size_t offset, size_t width, size_t height);
SdlInputWidget(SdlInputWidget&& other) noexcept;
SdlInputWidget(const SdlInputWidget& other) = delete;
~SdlInputWidget() = default;
SdlInputWidget& operator=(const SdlInputWidget& other) = delete;
SdlInputWidget& operator=(SdlInputWidget&& other) = delete;
bool fill_label(SDL_Renderer* renderer, SDL_Color color);
bool update_label(SDL_Renderer* renderer);
bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
bool set_highlight(SDL_Renderer* renderer, bool highlight);
bool update_input(SDL_Renderer* renderer);
bool resize_input(size_t size);
bool set_str(SDL_Renderer* renderer, const std::string& text);
bool remove_str(SDL_Renderer* renderer, size_t count);
bool append_str(SDL_Renderer* renderer, const std::string& text);
[[nodiscard]] const SDL_Rect& input_rect() const;
[[nodiscard]] std::string value() const;
[[nodiscard]] bool readonly() const;
protected:
bool update_input(SDL_Renderer* renderer, SDL_Color fgcolor);
private:
Uint32 _flags;
std::string _text;
std::string _text_label;
SdlWidget _label;
SdlWidget _input;
bool _highlight;
bool _mouseover;
};

View File

@@ -0,0 +1,299 @@
#include <cassert>
#include <limits>
#include <algorithm>
#include <cinttypes>
#include "sdl_input_widgets.hpp"
static const Uint32 vpadding = 5;
SdlInputWidgetList::SdlInputWidgetList(const std::string& title,
const std::vector<std::string>& labels,
const std::vector<std::string>& initial,
const std::vector<Uint32>& flags)
: _window(nullptr), _renderer(nullptr)
{
assert(labels.size() == initial.size());
assert(labels.size() == flags.size());
const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
const std::vector<std::string> buttonlabels = { "accept", "cancel" };
const size_t widget_width = 300;
const size_t widget_heigth = 50;
const size_t total_width = widget_width + widget_width;
const size_t input_height = labels.size() * (widget_heigth + vpadding) + vpadding;
const size_t total_height = input_height + widget_heigth;
assert(total_width <= INT32_MAX);
assert(total_height <= INT32_MAX);
Uint32 wflags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS;
auto rc =
SDL_CreateWindowAndRenderer(static_cast<int>(total_width), static_cast<int>(total_height),
wflags, &_window, &_renderer);
if (rc != 0)
widget_log_error(rc, "SDL_CreateWindowAndRenderer");
else
{
SDL_SetWindowTitle(_window, title.c_str());
for (size_t x = 0; x < labels.size(); x++)
_list.emplace_back(_renderer, labels.at(x), initial.at(x), flags.at(x), x, widget_width,
widget_heigth);
_buttons.populate(_renderer, buttonlabels, buttonids, total_width,
static_cast<Sint32>(input_height), static_cast<Sint32>(widget_width),
static_cast<Sint32>(widget_heigth));
_buttons.set_highlight(0);
}
}
ssize_t SdlInputWidgetList::next(ssize_t current)
{
size_t iteration = 0;
auto val = static_cast<size_t>(current);
do
{
if (iteration >= _list.size())
return -1;
if (iteration == 0)
{
if (current < 0)
val = 0;
else
val++;
}
else
val++;
iteration++;
val %= _list.size();
} while (!valid(static_cast<ssize_t>(val)));
return static_cast<ssize_t>(val);
}
bool SdlInputWidgetList::valid(ssize_t current) const
{
if (current < 0)
return false;
auto s = static_cast<size_t>(current);
if (s >= _list.size())
return false;
return !_list.at(s).readonly();
}
SdlInputWidget* SdlInputWidgetList::get(ssize_t index)
{
if (index < 0)
return nullptr;
auto s = static_cast<size_t>(index);
if (s >= _list.size())
return nullptr;
return &_list.at(s);
}
SdlInputWidgetList::~SdlInputWidgetList()
{
_list.clear();
_buttons.clear();
SDL_DestroyRenderer(_renderer);
SDL_DestroyWindow(_window);
}
bool SdlInputWidgetList::update(SDL_Renderer* renderer)
{
for (auto& btn : _list)
{
if (!btn.update_label(renderer))
return false;
if (!btn.update_input(renderer))
return false;
}
return _buttons.update(renderer);
}
ssize_t SdlInputWidgetList::get_index(const SDL_MouseButtonEvent& button)
{
const Sint32 x = button.x;
const Sint32 y = button.y;
assert(_list.size() <= std::numeric_limits<ssize_t>::max());
for (size_t i = 0; i < _list.size(); i++)
{
auto& cur = _list.at(i);
auto r = cur.input_rect();
if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
return static_cast<ssize_t>(i);
}
return -1;
}
int SdlInputWidgetList::run(std::vector<std::string>& result)
{
int res = -1;
ssize_t LastActiveTextInput = -1;
ssize_t CurrentActiveTextInput = next(-1);
if (!_window || !_renderer)
return -2;
try
{
bool running = true;
std::vector<SDL_Keycode> pressed;
while (running)
{
if (!clear_window(_renderer))
throw;
if (!update(_renderer))
throw;
if (!_buttons.update(_renderer))
throw;
SDL_Event event = {};
SDL_WaitEvent(&event);
switch (event.type)
{
case SDL_KEYUP:
{
auto it = std::remove(pressed.begin(), pressed.end(), event.key.keysym.sym);
pressed.erase(it, pressed.end());
switch (event.key.keysym.sym)
{
case SDLK_BACKSPACE:
{
auto cur = get(CurrentActiveTextInput);
if (cur)
{
if (!cur->remove_str(_renderer, 1))
throw;
}
}
break;
case SDLK_TAB:
CurrentActiveTextInput = next(CurrentActiveTextInput);
break;
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_KP_ENTER:
running = false;
res = INPUT_BUTTON_ACCEPT;
break;
case SDLK_ESCAPE:
running = false;
res = INPUT_BUTTON_CANCEL;
break;
case SDLK_v:
if (pressed.size() == 2)
{
if ((pressed.at(0) == SDLK_LCTRL) || (pressed.at(0) == SDLK_RCTRL))
{
auto cur = get(CurrentActiveTextInput);
if (cur)
{
auto text = SDL_GetClipboardText();
cur->set_str(_renderer, text);
}
}
}
break;
default:
break;
}
}
break;
case SDL_KEYDOWN:
pressed.push_back(event.key.keysym.sym);
break;
case SDL_TEXTINPUT:
{
auto cur = get(CurrentActiveTextInput);
if (cur)
{
if (!cur->append_str(_renderer, event.text.text))
throw;
}
}
break;
case SDL_MOUSEMOTION:
{
auto TextInputIndex = get_index(event.button);
for (auto& cur : _list)
{
if (!cur.set_mouseover(_renderer, false))
throw;
}
if (TextInputIndex >= 0)
{
auto& cur = _list.at(static_cast<size_t>(TextInputIndex));
if (!cur.set_mouseover(_renderer, true))
throw;
}
_buttons.set_mouseover(event.button.x, event.button.y);
}
break;
case SDL_MOUSEBUTTONDOWN:
{
auto val = get_index(event.button);
if (valid(val))
CurrentActiveTextInput = val;
auto button = _buttons.get_selected(event.button);
if (button)
{
running = false;
if (button->id() == INPUT_BUTTON_CANCEL)
res = INPUT_BUTTON_CANCEL;
else
res = INPUT_BUTTON_ACCEPT;
}
}
break;
case SDL_QUIT:
res = INPUT_BUTTON_CANCEL;
running = false;
break;
default:
break;
}
if (LastActiveTextInput != CurrentActiveTextInput)
{
if (CurrentActiveTextInput < 0)
SDL_StopTextInput();
else
SDL_StartTextInput();
LastActiveTextInput = CurrentActiveTextInput;
}
for (auto& cur : _list)
{
if (!cur.set_highlight(_renderer, false))
throw;
}
auto cur = get(CurrentActiveTextInput);
if (cur)
{
if (!cur->set_highlight(_renderer, true))
throw;
}
SDL_RenderPresent(_renderer);
}
for (auto& cur : _list)
result.push_back(cur.value());
}
catch (...)
{
res = -2;
}
return res;
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <string>
#include <vector>
#include <SDL.h>
#include "sdl_input.hpp"
#include "sdl_buttons.hpp"
class SdlInputWidgetList
{
public:
SdlInputWidgetList(const std::string& title, const std::vector<std::string>& labels,
const std::vector<std::string>& initial, const std::vector<Uint32>& flags);
SdlInputWidgetList(const SdlInputWidgetList& other) = delete;
SdlInputWidgetList(SdlInputWidgetList&& other) = delete;
SdlInputWidgetList& operator=(const SdlInputWidgetList& other) = delete;
SdlInputWidgetList& operator=(SdlInputWidgetList&& other) = delete;
virtual ~SdlInputWidgetList();
int run(std::vector<std::string>& result);
protected:
bool update(SDL_Renderer* renderer);
ssize_t get_index(const SDL_MouseButtonEvent& button);
private:
enum
{
INPUT_BUTTON_ACCEPT = 1,
INPUT_BUTTON_CANCEL = -2
};
ssize_t next(ssize_t current);
[[nodiscard]] bool valid(ssize_t current) const;
SdlInputWidget* get(ssize_t index);
SDL_Window* _window;
SDL_Renderer* _renderer;
std::vector<SdlInputWidget> _list;
SdlButtonList _buttons;
};

View File

@@ -0,0 +1,72 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 <cassert>
#include <string>
#include <utility>
#include <SDL.h>
#include <SDL_ttf.h>
#include "sdl_select.hpp"
#include "sdl_widget.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
#include "sdl_input_widgets.hpp"
static const SDL_Color labelmouseovercolor = { 0, 0x80, 0, 0x60 };
static const SDL_Color labelbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
static const SDL_Color labelhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
SdlSelectWidget::SdlSelectWidget(SDL_Renderer* renderer, std::string label, SDL_Rect rect)
: SdlWidget(renderer, rect, true), _text(std::move(label)), _mouseover(false), _highlight(false)
{
update_text(renderer);
}
SdlSelectWidget::SdlSelectWidget(SdlSelectWidget&& other) noexcept = default;
SdlSelectWidget::~SdlSelectWidget() = default;
bool SdlSelectWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
{
_mouseover = mouseOver;
return update_text(renderer);
}
bool SdlSelectWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
{
_highlight = highlight;
return update_text(renderer);
}
bool SdlSelectWidget::update_text(SDL_Renderer* renderer)
{
assert(renderer);
std::vector<SDL_Color> colors = { labelbackgroundcolor };
if (_highlight)
colors.push_back(labelhighlightcolor);
if (_mouseover)
colors.push_back(labelmouseovercolor);
if (!fill(renderer, colors))
return false;
return SdlWidget::update_text(renderer, _text, labelfontcolor);
}

View File

@@ -0,0 +1,47 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <string>
#include <vector>
#include <SDL.h>
#include "sdl_widget.hpp"
class SdlSelectWidget : public SdlWidget
{
public:
SdlSelectWidget(SDL_Renderer* renderer, std::string label, SDL_Rect rect);
SdlSelectWidget(SdlSelectWidget&& other) noexcept;
~SdlSelectWidget() override;
bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
bool set_highlight(SDL_Renderer* renderer, bool highlight);
bool update_text(SDL_Renderer* renderer);
SdlSelectWidget(const SdlSelectWidget& other) = delete;
SdlSelectWidget& operator=(const SdlSelectWidget& other) = delete;
SdlSelectWidget& operator=(SdlSelectWidget&& other) = delete;
private:
std::string _text;
bool _mouseover;
bool _highlight;
};

View File

@@ -0,0 +1,217 @@
#include <cassert>
#include <winpr/cast.h>
#include "sdl_selectlist.hpp"
static const Uint32 vpadding = 5;
SdlSelectList::SdlSelectList(const std::string& title, const std::vector<std::string>& labels)
: _window(nullptr), _renderer(nullptr)
{
const size_t widget_height = 50;
const size_t widget_width = 600;
const size_t total_height = labels.size() * (widget_height + vpadding) + vpadding;
const size_t height = total_height + widget_height;
assert(widget_width <= INT32_MAX);
assert(height <= INT32_MAX);
auto flags = WINPR_ASSERTING_INT_CAST(
uint32_t, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
auto rc = SDL_CreateWindowAndRenderer(static_cast<int>(widget_width), static_cast<int>(height),
flags, &_window, &_renderer);
if (rc != 0)
widget_log_error(rc, "SDL_CreateWindowAndRenderer");
else
{
SDL_SetWindowTitle(_window, title.c_str());
SDL_Rect rect = { 0, 0, widget_width, widget_height };
for (auto& label : labels)
{
_list.emplace_back(_renderer, label, rect);
rect.y += widget_height + vpadding;
}
const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
const std::vector<std::string> buttonlabels = { "accept", "cancel" };
_buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
static_cast<Sint32>(total_height), static_cast<Sint32>(widget_width / 2),
static_cast<Sint32>(widget_height));
_buttons.set_highlight(0);
}
}
SdlSelectList::~SdlSelectList()
{
_list.clear();
_buttons.clear();
SDL_DestroyRenderer(_renderer);
SDL_DestroyWindow(_window);
}
int SdlSelectList::run()
{
int res = -2;
ssize_t CurrentActiveTextInput = 0;
bool running = true;
if (!_window || !_renderer)
return -2;
try
{
while (running)
{
if (!clear_window(_renderer))
throw;
if (!update_text())
throw;
if (!_buttons.update(_renderer))
throw;
SDL_Event event = {};
SDL_WaitEvent(&event);
switch (event.type)
{
case SDL_KEYDOWN:
switch (event.key.keysym.sym)
{
case SDLK_UP:
case SDLK_BACKSPACE:
if (CurrentActiveTextInput > 0)
CurrentActiveTextInput--;
else
CurrentActiveTextInput =
WINPR_ASSERTING_INT_CAST(ssize_t, _list.size() - 1);
break;
case SDLK_DOWN:
case SDLK_TAB:
{
if (CurrentActiveTextInput < 0)
CurrentActiveTextInput = 0;
else
CurrentActiveTextInput++;
const auto s = _list.size();
if (s <= 0)
CurrentActiveTextInput = 0;
else
CurrentActiveTextInput =
CurrentActiveTextInput % WINPR_ASSERTING_INT_CAST(ssize_t, s);
}
break;
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_KP_ENTER:
running = false;
res = WINPR_ASSERTING_INT_CAST(int, CurrentActiveTextInput);
break;
case SDLK_ESCAPE:
running = false;
res = INPUT_BUTTON_CANCEL;
break;
default:
break;
}
break;
case SDL_MOUSEMOTION:
{
ssize_t TextInputIndex = get_index(event.button);
reset_mouseover();
if (TextInputIndex >= 0)
{
auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, TextInputIndex));
if (!cur.set_mouseover(_renderer, true))
throw;
}
_buttons.set_mouseover(event.button.x, event.button.y);
}
break;
case SDL_MOUSEBUTTONDOWN:
{
auto button = _buttons.get_selected(event.button);
if (button)
{
running = false;
if (button->id() == INPUT_BUTTON_CANCEL)
res = INPUT_BUTTON_CANCEL;
else
res = static_cast<int>(CurrentActiveTextInput);
}
else
{
CurrentActiveTextInput = get_index(event.button);
}
}
break;
case SDL_QUIT:
res = INPUT_BUTTON_CANCEL;
running = false;
break;
default:
break;
}
reset_highlight();
if (CurrentActiveTextInput >= 0)
{
auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, CurrentActiveTextInput));
if (!cur.set_highlight(_renderer, true))
throw;
}
SDL_RenderPresent(_renderer);
}
}
catch (...)
{
return -1;
}
return res;
}
ssize_t SdlSelectList::get_index(const SDL_MouseButtonEvent& button)
{
const Sint32 x = button.x;
const Sint32 y = button.y;
for (size_t i = 0; i < _list.size(); i++)
{
auto& cur = _list.at(i);
auto r = cur.rect();
if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
return WINPR_ASSERTING_INT_CAST(ssize_t, i);
}
return -1;
}
bool SdlSelectList::update_text()
{
for (auto& cur : _list)
{
if (!cur.update_text(_renderer))
return false;
}
return true;
}
void SdlSelectList::reset_mouseover()
{
for (auto& cur : _list)
{
cur.set_mouseover(_renderer, false);
}
}
void SdlSelectList::reset_highlight()
{
for (auto& cur : _list)
{
cur.set_highlight(_renderer, false);
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <string>
#include <vector>
#include <SDL.h>
#include "sdl_select.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
class SdlSelectList
{
public:
SdlSelectList(const std::string& title, const std::vector<std::string>& labels);
virtual ~SdlSelectList();
int run();
SdlSelectList(const SdlSelectList& other) = delete;
SdlSelectList(SdlSelectList&& other) = delete;
SdlSelectList& operator=(const SdlSelectList& other) = delete;
SdlSelectList& operator=(SdlSelectList&& other) = delete;
private:
enum
{
INPUT_BUTTON_ACCEPT = 0,
INPUT_BUTTON_CANCEL = -2
};
ssize_t get_index(const SDL_MouseButtonEvent& button);
bool update_text();
void reset_mouseover();
void reset_highlight();
SDL_Window* _window;
SDL_Renderer* _renderer;
std::vector<SdlSelectWidget> _list;
SdlButtonList _buttons;
};

View File

@@ -0,0 +1,291 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 <cassert>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <SDL.h>
#include <SDL_ttf.h>
#include "sdl_widget.hpp"
#include "../sdl_utils.hpp"
#include "res/sdl2_resource_manager.hpp"
#include <freerdp/log.h>
#if defined(WITH_SDL_IMAGE_DIALOGS)
#include <SDL_image.h>
#endif
#define TAG CLIENT_TAG("SDL.widget")
static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
static const Uint32 hpadding = 10;
SdlWidget::SdlWidget([[maybe_unused]] SDL_Renderer* renderer, SDL_Rect rect, bool input)
: _rect(rect), _input(input)
{
assert(renderer);
auto ops = SDL2ResourceManager::get(SDLResourceManager::typeFonts(),
"OpenSans-VariableFont_wdth,wght.ttf");
if (!ops)
widget_log_error(-1, "SDLResourceManager::get");
else
{
_font = TTF_OpenFontRW(ops, 1, 64);
if (!_font)
widget_log_error(-1, "TTF_OpenFontRW");
}
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlWidget::SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, SDL_RWops* ops) : _rect(rect)
{
if (ops)
{
_image = IMG_LoadTexture_RW(renderer, ops, 1);
if (!_image)
widget_log_error(-1, "IMG_LoadTextureTyped_RW");
}
}
#endif
SdlWidget::SdlWidget(SdlWidget&& other) noexcept
: _font(other._font), _image(other._image), _rect(other._rect), _input(other._input),
_wrap(other._wrap), _text_width(other._text_width)
{
other._font = nullptr;
other._image = nullptr;
}
SDL_Texture* SdlWidget::render_text(SDL_Renderer* renderer, const std::string& text,
SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
{
auto surface = TTF_RenderUTF8_Blended(_font, text.c_str(), fgcolor);
if (!surface)
{
widget_log_error(-1, "TTF_RenderText_Blended");
return nullptr;
}
auto texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
if (!texture)
{
widget_log_error(-1, "SDL_CreateTextureFromSurface");
return nullptr;
}
TTF_SizeUTF8(_font, text.c_str(), &src.w, &src.h);
/* Do some magic:
* - Add padding before and after text
* - if text is too long only show the last elements
* - if text is too short only update used space
*/
dst = _rect;
dst.x += hpadding;
dst.w -= 2 * hpadding;
const auto scale = static_cast<float>(dst.h) / static_cast<float>(src.h);
const auto sws = static_cast<float>(src.w) * scale;
const auto dws = static_cast<float>(dst.w) / scale;
if (static_cast<float>(dst.w) > sws)
dst.w = static_cast<int>(sws);
if (static_cast<float>(src.w) > dws)
{
src.x = src.w - static_cast<int>(dws);
src.w = static_cast<int>(dws);
}
return texture;
}
static int scale(int w, int h)
{
const auto dw = static_cast<double>(w);
const auto dh = static_cast<double>(h);
const auto scale = dh / dw;
const auto dr = dh * scale;
return static_cast<int>(dr);
}
SDL_Texture* SdlWidget::render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
{
Sint32 w = 0;
Sint32 h = 0;
TTF_SizeUTF8(_font, " ", &w, &h);
assert(_text_width <= UINT32_MAX);
auto surface = TTF_RenderUTF8_Blended_Wrapped(_font, text.c_str(), fgcolor,
static_cast<Uint32>(_text_width));
if (!surface)
{
widget_log_error(-1, "TTF_RenderText_Blended");
return nullptr;
}
src.w = surface->w;
src.h = surface->h;
auto texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
if (!texture)
{
widget_log_error(-1, "SDL_CreateTextureFromSurface");
return nullptr;
}
/* Do some magic:
* - Add padding before and after text
* - if text is too long only show the last elements
* - if text is too short only update used space
*/
dst = _rect;
dst.x += hpadding;
dst.w -= 2 * hpadding;
auto dh = scale(src.w, src.h);
dst.h = std::min<int>(dh, dst.h);
return texture;
}
SdlWidget::~SdlWidget()
{
TTF_CloseFont(_font);
if (_image)
SDL_DestroyTexture(_image);
}
bool SdlWidget::error_ex(Sint32 res, const char* what, const char* file, size_t line,
const char* fkt)
{
static wLog* log = nullptr;
if (!log)
log = WLog_Get(TAG);
return sdl_log_error_ex(res, log, what, file, line, fkt);
}
static bool draw_rect(SDL_Renderer* renderer, const SDL_Rect* rect, SDL_Color color)
{
const int drc = SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const int rc = SDL_RenderFillRect(renderer, rect);
return !widget_log_error(rc, "SDL_RenderFillRect");
}
bool SdlWidget::fill(SDL_Renderer* renderer, SDL_Color color)
{
std::vector<SDL_Color> colors = { color };
return fill(renderer, colors);
}
bool SdlWidget::fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors)
{
assert(renderer);
SDL_BlendMode mode = SDL_BLENDMODE_INVALID;
SDL_GetRenderDrawBlendMode(renderer, &mode);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
for (auto color : colors)
{
draw_rect(renderer, &_rect, color);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
}
SDL_SetRenderDrawBlendMode(renderer, mode);
return true;
}
bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
SDL_Color bgcolor)
{
assert(renderer);
if (!fill(renderer, bgcolor))
return false;
return update_text(renderer, text, fgcolor);
}
bool SdlWidget::wrap() const
{
return _wrap;
}
bool SdlWidget::set_wrap(bool wrap, size_t width)
{
_wrap = wrap;
_text_width = width;
return _wrap;
}
const SDL_Rect& SdlWidget::rect() const
{
return _rect;
}
bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor)
{
if (text.empty())
return true;
SDL_Rect src{};
SDL_Rect dst{};
SDL_Texture* texture = nullptr;
if (_image)
{
texture = _image;
dst = _rect;
auto rc = SDL_QueryTexture(_image, nullptr, nullptr, &src.w, &src.h);
if (rc < 0)
widget_log_error(rc, "SDL_QueryTexture");
}
else if (_wrap)
texture = render_text_wrapped(renderer, text, fgcolor, src, dst);
else
texture = render_text(renderer, text, fgcolor, src, dst);
if (!texture)
return false;
const int rc = SDL_RenderCopy(renderer, texture, &src, &dst);
if (!_image)
SDL_DestroyTexture(texture);
if (rc < 0)
return !widget_log_error(rc, "SDL_RenderCopy");
return true;
}
bool clear_window(SDL_Renderer* renderer)
{
assert(renderer);
const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
backgroundcolor.b, backgroundcolor.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const int rcls = SDL_RenderClear(renderer);
return !widget_log_error(rcls, "SDL_RenderClear");
}

View File

@@ -0,0 +1,90 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <string>
#include <vector>
#include <SDL.h>
#include <SDL_ttf.h>
#if defined(_MSC_VER)
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
#if !defined(HAS_NOEXCEPT)
#if defined(__clang__)
#if __has_feature(cxx_noexcept)
#define HAS_NOEXCEPT
#endif
#elif defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ * 10 + __GNUC_MINOR__ >= 46 || \
defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026
#define HAS_NOEXCEPT
#endif
#endif
#ifndef HAS_NOEXCEPT
#define noexcept
#endif
class SdlWidget
{
public:
SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, bool input);
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, SDL_RWops* ops);
#endif
SdlWidget(SdlWidget&& other) noexcept;
virtual ~SdlWidget();
bool fill(SDL_Renderer* renderer, SDL_Color color);
bool fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors);
bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor);
bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
SDL_Color bgcolor);
[[nodiscard]] bool wrap() const;
bool set_wrap(bool wrap = true, size_t width = 0);
[[nodiscard]] const SDL_Rect& rect() const;
#define widget_log_error(res, what) SdlWidget::error_ex(res, what, __FILE__, __LINE__, __func__)
static bool error_ex(Sint32 res, const char* what, const char* file, size_t line,
const char* fkt);
SdlWidget(const SdlWidget& other) = delete;
SdlWidget& operator=(const SdlWidget& other) = delete;
SdlWidget& operator=(SdlWidget&& other) = delete;
private:
SDL_Texture* render_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
SDL_Rect& src, SDL_Rect& dst);
SDL_Texture* render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst);
TTF_Font* _font = nullptr;
SDL_Texture* _image = nullptr;
SDL_Rect _rect = {};
bool _input = false;
bool _wrap = false;
size_t _text_width = 0;
};
bool clear_window(SDL_Renderer* renderer);

View File

@@ -0,0 +1,25 @@
set(MODULE_NAME "TestSDL")
set(MODULE_PREFIX "TEST_SDL")
set(DRIVER ${MODULE_NAME}.cpp)
disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR})
set(TEST_SRCS TestSDLDialogs.cpp)
create_test_sourcelist(SRCS ${DRIVER} ${TEST_SRCS})
add_library(${MODULE_NAME} ${SRCS})
list(APPEND LIBS dialogs)
target_link_libraries(${MODULE_NAME} ${LIBS})
set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
foreach(test ${TESTS})
get_filename_component(TestName ${test} NAME_WE)
add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
endforeach()
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")

View File

@@ -0,0 +1,99 @@
#include "../sdl_selectlist.hpp"
#include "../sdl_input_widgets.hpp"
#include <freerdp/api.h>
#include <winpr/wlog.h>
BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
const char* fkt)
{
return FALSE;
}
static bool test_input_dialog()
{
const auto title = "sometitle";
std::vector<std::string> labels;
std::vector<std::string> initial;
std::vector<Uint32> flags;
for (size_t x = 0; x < 12; x++)
{
labels.push_back("label" + std::to_string(x));
initial.push_back(std::to_string(x));
Uint32 flag = 0;
if ((x % 2) != 0)
flag |= SdlInputWidget::SDL_INPUT_MASK;
if ((x % 3) == 0)
flag |= SdlInputWidget::SDL_INPUT_READONLY;
flags.push_back(flag);
}
SdlInputWidgetList list{ title, labels, initial, flags };
std::vector<std::string> result;
auto rc = list.run(result);
if (rc < 0)
{
return false;
}
if (result.size() != labels.size())
{
return false;
}
return true;
}
static bool test_select_dialog()
{
const auto title = "sometitle";
std::vector<std::string> labels;
for (size_t x = 0; x < 12; x++)
{
labels.push_back("label" + std::to_string(x));
}
SdlSelectList list{ title, labels };
auto rc = list.run();
if (rc < 0)
{
return false;
}
if (static_cast<size_t>(rc) >= labels.size())
return false;
return true;
}
extern "C"
{
FREERDP_API int TestSDLDialogs(int argc, char* argv[]);
}
int TestSDLDialogs(int argc, char* argv[])
{
int rc = 0;
(void)argc;
(void)argv;
#if 0
SDL_Init(SDL_INIT_VIDEO);
try
{
#if 1
if (!test_input_dialog())
throw -1;
#endif
#if 1
if (!test_select_dialog())
throw -2;
#endif
}
catch (int e)
{
rc = e;
}
SDL_Quit();
#endif
return rc;
}

View File

@@ -0,0 +1,15 @@
set(DEPS
../../../common/man/freerdp-global-options.1
../../../common/man/freerdp-global-envvar.1
../../common/man/sdl-freerdp-config.1
../../../common/man/freerdp-global-config.1
../../common/man/sdl-global-config.1
../../common/man/sdl-freerdp-examples.1
../../../common/man/freerdp-global-links.1
)
include(GetSysconfDir)
get_sysconf_dir("" SYSCONF_DIR)
set(SDL_WIKI_BASE_URL "https://wiki.libsdl.org/SDL2")
set(VAR_NAMES "VENDOR" "PRODUCT" "VENDOR_PRODUCT" "SYSCONF_DIR" "SDL_WIKI_BASE_URL")
generate_and_install_freerdp_man_from_xml(${MODULE_NAME} "1" "${DEPS}" "${VAR_NAMES}")

View File

@@ -0,0 +1,15 @@
.TH "@MANPAGE_NAME@" "1" "@MAN_TODAY@" "freerdp" "@MANPAGE_NAME@"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.nh
.ad l
.SH "NAME"
@MANPAGE_NAME@ \- FreeRDP SDL client
.SH "SYNOPSIS"
.PP
\fB@MANPAGE_NAME@\fR
[file] [options] [/v:server[:port]]
.SH "DESCRIPTION"
.PP
\fB@MANPAGE_NAME@\fR
is an SDL Remote Desktop Protocol (RDP) client which is part of the FreeRDP project\&. An RDP server is built\-in to many editions of Windows\&. Alternative servers included ogon, gnome\-remote\-desktop, xrdp and VRDP (VirtualBox)\&.

View File

@@ -0,0 +1,83 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client Channels
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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 <freerdp/config.h>
#include <winpr/assert.h>
#include <freerdp/client/rail.h>
#include <freerdp/client/cliprdr.h>
#include <freerdp/client/disp.h>
#include "sdl_channels.hpp"
#include "sdl_freerdp.hpp"
#include "sdl_disp.hpp"
void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
{
auto sdl = get_context(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)
{
auto clip = reinterpret_cast<CliprdrClientContext*>(e->pInterface);
WINPR_ASSERT(clip);
clip->custom = context;
}
else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
{
auto disp = reinterpret_cast<DispClientContext*>(e->pInterface);
WINPR_ASSERT(disp);
sdl->disp.init(disp);
}
else
freerdp_client_OnChannelConnectedEventHandler(context, e);
}
void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
{
auto sdl = get_context(context);
WINPR_ASSERT(sdl);
WINPR_ASSERT(e);
// TODO(nin): 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)
{
auto clip = reinterpret_cast<CliprdrClientContext*>(e->pInterface);
WINPR_ASSERT(clip);
clip->custom = nullptr;
}
else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
{
auto disp = reinterpret_cast<DispClientContext*>(e->pInterface);
WINPR_ASSERT(disp);
sdl->disp.uninit(disp);
}
else
freerdp_client_OnChannelDisconnectedEventHandler(context, e);
}

View File

@@ -0,0 +1,29 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client Channels
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <freerdp/freerdp.h>
#include <freerdp/client/channels.h>
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);

View File

@@ -0,0 +1,30 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL config template
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 Thinast Technologies GmbH
*
* 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.
*/
#pragma once
#cmakedefine WITH_WEBVIEW
static const char SDL_CLIENT_NAME[] = "@SDL_CLIENT_BINARY_NAME@";
static const char SDL_CLIENT_VERSION[] = "@FREERDP_VERSION_FULL@ (@GIT_REVISION@)";
static const char SDL_CLIENT_VENDOR[] = "@VENDOR@";
static const char SDL_CLIENT_UUID[] = "@SDL_CLIENT_UUID@";
static const char SDL_CLIENT_COPYRIGHT[] = "FreeRDP project";
static const char SDL_CLIENT_URL[] = "@PROJECT_URL@";
static const char SDL_CLIENT_TYPE[] = "application";

View File

@@ -0,0 +1,481 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Display Control Channel
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 <vector>
#include <winpr/sysinfo.h>
#include <winpr/assert.h>
#include <freerdp/gdi/gdi.h>
#include <SDL.h>
#include "sdl_disp.hpp"
#include "sdl_kbd.hpp"
#include "sdl_utils.hpp"
#include "sdl_freerdp.hpp"
#include <freerdp/log.h>
#define TAG CLIENT_TAG("sdl.disp")
static constexpr UINT64 RESIZE_MIN_DELAY = 200; /* minimum delay in ms between two resizes */
static constexpr unsigned MAX_RETRIES = 5;
BOOL sdlDispContext::settings_changed()
{
auto settings = _sdl->context()->settings;
WINPR_ASSERT(settings);
if (_lastSentWidth != _targetWidth)
return TRUE;
if (_lastSentHeight != _targetHeight)
return TRUE;
if (_lastSentDesktopOrientation !=
freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation))
return TRUE;
if (_lastSentDesktopScaleFactor !=
freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor))
return TRUE;
if (_lastSentDeviceScaleFactor !=
freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor))
return TRUE;
/* TODO
if (_fullscreen != _sdl->fullscreen)
return TRUE;
*/
return FALSE;
}
BOOL sdlDispContext::update_last_sent()
{
WINPR_ASSERT(_sdl);
auto settings = _sdl->context()->settings;
WINPR_ASSERT(settings);
_lastSentWidth = _targetWidth;
_lastSentHeight = _targetHeight;
_lastSentDesktopOrientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
_lastSentDesktopScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
_lastSentDeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
// TODO _fullscreen = _sdl->fullscreen;
return TRUE;
}
BOOL sdlDispContext::sendResize()
{
DISPLAY_CONTROL_MONITOR_LAYOUT layout = {};
auto settings = _sdl->context()->settings;
if (!settings)
return FALSE;
if (!_activated || !_disp)
return TRUE;
if (GetTickCount64() - _lastSentDate < RESIZE_MIN_DELAY)
return TRUE;
_lastSentDate = GetTickCount64();
if (!settings_changed())
return TRUE;
const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
if (_sdl->fullscreen && (mcount > 0))
{
auto monitors = static_cast<const rdpMonitor*>(
freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray));
if (sendLayout(monitors, mcount) != CHANNEL_RC_OK)
return FALSE;
}
else
{
_waitingResize = TRUE;
layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
layout.Top = layout.Left = 0;
layout.Width = WINPR_ASSERTING_INT_CAST(uint32_t, _targetWidth);
layout.Height = WINPR_ASSERTING_INT_CAST(uint32_t, _targetHeight);
layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
layout.DesktopScaleFactor =
freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
layout.PhysicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, _targetWidth);
layout.PhysicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, _targetHeight);
if (IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, 1, &layout) !=
CHANNEL_RC_OK)
return FALSE;
}
return update_last_sent();
}
BOOL sdlDispContext::set_window_resizable()
{
_sdl->update_resizeable(TRUE);
return TRUE;
}
static BOOL sdl_disp_check_context(void* context, SdlContext** ppsdl, sdlDispContext** ppsdlDisp,
rdpSettings** ppSettings)
{
if (!context)
return FALSE;
auto sdl = get_context(context);
if (!sdl)
return FALSE;
if (!sdl->context()->settings)
return FALSE;
*ppsdl = sdl;
*ppsdlDisp = &sdl->disp;
*ppSettings = sdl->context()->settings;
return TRUE;
}
void sdlDispContext::OnActivated(void* context, const ActivatedEventArgs* e)
{
SdlContext* sdl = nullptr;
sdlDispContext* sdlDisp = nullptr;
rdpSettings* settings = nullptr;
if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
return;
sdlDisp->_waitingResize = FALSE;
if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
{
sdlDisp->set_window_resizable();
if (e->firstActivation)
return;
sdlDisp->addTimer();
}
}
void sdlDispContext::OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
{
SdlContext* sdl = nullptr;
sdlDispContext* sdlDisp = nullptr;
rdpSettings* settings = nullptr;
WINPR_UNUSED(e);
if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
return;
sdlDisp->_waitingResize = FALSE;
if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
{
sdlDisp->set_window_resizable();
sdlDisp->addTimer();
}
}
Uint32 sdlDispContext::OnTimer(Uint32 interval, void* param)
{
auto ctx = static_cast<sdlDispContext*>(param);
if (!ctx)
return 0;
SdlContext* sdl = ctx->_sdl;
if (!sdl)
return 0;
sdlDispContext* sdlDisp = nullptr;
rdpSettings* settings = nullptr;
if (!sdl_disp_check_context(sdl->context(), &sdl, &sdlDisp, &settings))
return 0;
WLog_Print(sdl->log, WLOG_TRACE, "checking for display changes...");
if (!sdlDisp->_activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
return 0;
else
{
auto rc = sdlDisp->sendResize();
if (!rc)
WLog_Print(sdl->log, WLOG_TRACE, "sent new display layout, result %d", rc);
}
if (sdlDisp->_timer_retries++ >= MAX_RETRIES)
{
WLog_Print(sdl->log, WLOG_TRACE, "deactivate timer, retries exceeded");
return 0;
}
WLog_Print(sdl->log, WLOG_TRACE, "fire timer one more time");
return interval;
}
UINT sdlDispContext::sendLayout(const rdpMonitor* monitors, size_t nmonitors)
{
UINT ret = CHANNEL_RC_OK;
WINPR_ASSERT(monitors);
WINPR_ASSERT(nmonitors > 0);
auto settings = _sdl->context()->settings;
WINPR_ASSERT(settings);
std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT> layouts;
layouts.resize(nmonitors);
for (size_t i = 0; i < nmonitors; i++)
{
auto monitor = &monitors[i];
auto& layout = layouts.at(i);
layout.Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
layout.Left = monitor->x;
layout.Top = monitor->y;
layout.Width = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width);
layout.Height = WINPR_ASSERTING_INT_CAST(uint32_t, 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 =
freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
}
WINPR_ASSERT(_disp);
const size_t len = layouts.size();
WINPR_ASSERT(len <= UINT32_MAX);
ret = IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, static_cast<UINT32>(len),
layouts.data());
return ret;
}
BOOL sdlDispContext::addTimer()
{
if (SDL_WasInit(SDL_INIT_TIMER) == 0)
return FALSE;
SDL_RemoveTimer(_timer);
WLog_Print(_sdl->log, WLOG_TRACE, "adding new display check timer");
_timer_retries = 0;
sendResize();
_timer = SDL_AddTimer(1000, sdlDispContext::OnTimer, this);
return TRUE;
}
#if SDL_VERSION_ATLEAST(2, 0, 10)
BOOL sdlDispContext::handle_display_event(const SDL_DisplayEvent* ev)
{
WINPR_ASSERT(ev);
switch (ev->event)
{
#if SDL_VERSION_ATLEAST(2, 0, 14)
case SDL_DISPLAYEVENT_CONNECTED:
SDL_Log("A new display with id %u was connected", ev->display);
return TRUE;
case SDL_DISPLAYEVENT_DISCONNECTED:
SDL_Log("The display with id %u was disconnected", ev->display);
return TRUE;
#endif
case SDL_DISPLAYEVENT_ORIENTATION:
SDL_Log("The orientation of display with id %u was changed", ev->display);
return TRUE;
default:
return TRUE;
}
}
#endif
BOOL sdlDispContext::handle_window_event(const SDL_WindowEvent* ev)
{
WINPR_ASSERT(ev);
#if defined(WITH_DEBUG_SDL_EVENTS)
SDL_Log("got windowEvent %s [0x%08" PRIx32 "]", sdl_window_event_str(ev->event).c_str(),
ev->event);
#endif
auto bordered = freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_Decorations)
? SDL_TRUE
: SDL_FALSE;
auto it = _sdl->windows.find(ev->windowID);
if (it != _sdl->windows.end())
it->second.setBordered(bordered);
switch (ev->event)
{
case SDL_WINDOWEVENT_HIDDEN:
case SDL_WINDOWEVENT_MINIMIZED:
return gdi_send_suppress_output(_sdl->context()->gdi, TRUE);
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SHOWN:
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
return gdi_send_suppress_output(_sdl->context()->gdi, FALSE);
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
_targetWidth = ev->data1;
_targetHeight = ev->data2;
return addTimer();
case SDL_WINDOWEVENT_LEAVE:
WINPR_ASSERT(_sdl);
_sdl->input.keyboard_grab(ev->windowID, false);
return TRUE;
case SDL_WINDOWEVENT_ENTER:
WINPR_ASSERT(_sdl);
_sdl->input.keyboard_grab(ev->windowID, true);
return _sdl->input.keyboard_focus_in();
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_WINDOWEVENT_TAKE_FOCUS:
return _sdl->input.keyboard_focus_in();
default:
return TRUE;
}
}
UINT sdlDispContext::DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
{
/* we're called only if dynamic resolution update is activated */
WINPR_ASSERT(disp);
auto sdlDisp = reinterpret_cast<sdlDispContext*>(disp->custom);
return sdlDisp->DisplayControlCaps(maxNumMonitors, maxMonitorAreaFactorA,
maxMonitorAreaFactorB);
}
UINT sdlDispContext::DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
UINT32 maxMonitorAreaFactorB)
{
auto settings = _sdl->context()->settings;
WINPR_ASSERT(settings);
WLog_DBG(TAG,
"DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
" MaxMonitorAreaFactorB: %" PRIu32 "",
maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
_activated = TRUE;
if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
return CHANNEL_RC_OK;
WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
return set_window_resizable() ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
}
BOOL sdlDispContext::init(DispClientContext* disp)
{
if (!disp)
return FALSE;
auto settings = _sdl->context()->settings;
if (!settings)
return FALSE;
_disp = disp;
disp->custom = this;
if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
{
disp->DisplayControlCaps = sdlDispContext::DisplayControlCaps;
}
_sdl->update_resizeable(TRUE);
return TRUE;
}
BOOL sdlDispContext::uninit(DispClientContext* disp)
{
if (!disp)
return FALSE;
_disp = nullptr;
_sdl->update_resizeable(FALSE);
return TRUE;
}
sdlDispContext::sdlDispContext(SdlContext* sdl) : _sdl(sdl)
{
SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
WINPR_ASSERT(_sdl);
WINPR_ASSERT(_sdl->context()->settings);
WINPR_ASSERT(_sdl->context()->pubSub);
auto settings = _sdl->context()->settings;
auto pubSub = _sdl->context()->pubSub;
_lastSentWidth = _targetWidth =
WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth));
_lastSentHeight = _targetHeight =
WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
if (PubSub_SubscribeActivated(pubSub, sdlDispContext::OnActivated) < 0)
throw std::exception();
if (PubSub_SubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset) < 0)
throw std::exception();
addTimer();
}
sdlDispContext::~sdlDispContext()
{
wPubSub* pubSub = _sdl->context()->pubSub;
WINPR_ASSERT(pubSub);
PubSub_UnsubscribeActivated(pubSub, sdlDispContext::OnActivated);
PubSub_UnsubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
SDL_RemoveTimer(_timer);
SDL_Quit();
}

View File

@@ -0,0 +1,82 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Display Control Channel
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <freerdp/types.h>
#include <freerdp/event.h>
#include <freerdp/client/disp.h>
#include "sdl_types.hpp"
#include <SDL.h>
class sdlDispContext
{
public:
explicit sdlDispContext(SdlContext* sdl);
sdlDispContext(const sdlDispContext& other) = delete;
sdlDispContext(sdlDispContext&& other) = delete;
~sdlDispContext();
sdlDispContext& operator=(const sdlDispContext& other) = delete;
sdlDispContext& operator=(sdlDispContext&& other) = delete;
BOOL init(DispClientContext* disp);
BOOL uninit(DispClientContext* disp);
#if SDL_VERSION_ATLEAST(2, 0, 10)
BOOL handle_display_event(const SDL_DisplayEvent* ev);
#endif
BOOL handle_window_event(const SDL_WindowEvent* ev);
private:
UINT DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
UINT32 maxMonitorAreaFactorB);
BOOL set_window_resizable();
BOOL sendResize();
BOOL settings_changed();
BOOL update_last_sent();
UINT sendLayout(const rdpMonitor* monitors, size_t nmonitors);
BOOL addTimer();
static UINT DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB);
static void OnActivated(void* context, const ActivatedEventArgs* e);
static void OnGraphicsReset(void* context, const GraphicsResetEventArgs* e);
static Uint32 SDLCALL OnTimer(Uint32 interval, void* param);
SdlContext* _sdl = nullptr;
DispClientContext* _disp = nullptr;
int _lastSentWidth = -1;
int _lastSentHeight = -1;
UINT64 _lastSentDate = 0;
int _targetWidth = -1;
int _targetHeight = -1;
BOOL _activated = FALSE;
BOOL _waitingResize = FALSE;
UINT16 _lastSentDesktopOrientation = 0;
UINT32 _lastSentDesktopScaleFactor = 0;
UINT32 _lastSentDeviceScaleFactor = 0;
SDL_TimerID _timer = 0;
unsigned _timer_retries = 0;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <memory>
#include <thread>
#include <map>
#include <atomic>
#include <freerdp/freerdp.h>
#include <freerdp/client/rdpei.h>
#include <freerdp/client/rail.h>
#include <freerdp/client/cliprdr.h>
#include <freerdp/client/rdpgfx.h>
#include <SDL.h>
#include <SDL_video.h>
#include "sdl_types.hpp"
#include "sdl_disp.hpp"
#include "sdl_kbd.hpp"
#include "sdl_utils.hpp"
#include "sdl_window.hpp"
#include "dialogs/sdl_connection_dialog.hpp"
using SDLSurfacePtr = std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)>;
using SDLPixelFormatPtr = std::unique_ptr<SDL_PixelFormat, decltype(&SDL_FreeFormat)>;
class SdlContext
{
public:
explicit SdlContext(rdpContext* context);
SdlContext(const SdlContext& other) = delete;
SdlContext(SdlContext&& other) = delete;
~SdlContext() = default;
SdlContext& operator=(const SdlContext& other) = delete;
SdlContext& operator=(SdlContext&& other) = delete;
private:
rdpContext* _context;
public:
wLog* log;
/* SDL */
bool fullscreen = false;
bool resizeable = false;
bool grab_mouse = false;
bool grab_kbd = false;
bool grab_kbd_enabled = true;
std::map<Uint32, SdlWindow> windows;
CriticalSection critical;
std::thread thread;
WinPREvent initialize;
WinPREvent initialized;
WinPREvent update_complete;
WinPREvent windows_created;
int exit_code = -1;
sdlDispContext disp;
sdlInput input;
SDLSurfacePtr primary;
SDLPixelFormatPtr primary_format;
Uint32 sdl_pixel_format = 0;
std::unique_ptr<SDLConnectionDialog> connection_dialog;
std::atomic<bool> rdp_thread_running;
BOOL update_resizeable(BOOL enable);
BOOL update_fullscreen(BOOL enter);
BOOL update_minimize();
[[nodiscard]] rdpContext* context() const;
[[nodiscard]] rdpClientContext* common() const;
};

View File

@@ -0,0 +1,612 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP SDL keyboard helper
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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 <winpr/cast.h>
#include "sdl_kbd.hpp"
#include "sdl_disp.hpp"
#include "sdl_freerdp.hpp"
#include "sdl_utils.hpp"
#include "sdl_prefs.hpp"
#include "sdl_touch.hpp"
#include <map>
#include <freerdp/utils/string.h>
#include <freerdp/scancode.h>
#include <freerdp/log.h>
#define TAG CLIENT_TAG("SDL.kbd")
using scancode_entry_t = struct
{
Uint32 sdl;
const char* sdl_name;
UINT32 rdp;
const char* rdp_name;
};
#define STR(x) #x
#define ENTRY(x, y) { x, STR(x), y, #y }
// clang-format off
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_SUBTRACT),
ENTRY(SDL_SCANCODE_KP_PLUS, RDP_SCANCODE_ADD),
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, RDP_SCANCODE_OEM_PLUS),
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),
// 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)
};
// clang-format on
static UINT16 sdl_get_kbd_flags()
{
UINT16 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 sdlInput::keyboard_sync_state()
{
const auto syncFlags = sdl_get_kbd_flags();
return freerdp_input_send_synchronize_event(_sdl->context()->input, syncFlags);
}
BOOL sdlInput::keyboard_focus_in()
{
auto input = _sdl->context()->input;
WINPR_ASSERT(input);
auto syncFlags = sdl_get_kbd_flags();
freerdp_input_send_focus_in_event(input, WINPR_ASSERTING_INT_CAST(UINT16, syncFlags));
/* finish with a mouse pointer position like mstsc.exe if required */
// TODO: fullscreen/remote app
int x = 0;
int y = 0;
if (_sdl->fullscreen)
{
SDL_GetGlobalMouseState(&x, &y);
}
else
{
SDL_GetMouseState(&x, &y);
}
auto w = SDL_GetMouseFocus();
if (w)
{
auto id = SDL_GetWindowID(w);
sdl_scale_coordinates(_sdl, id, &x, &y, TRUE, TRUE);
}
return freerdp_client_send_button_event(_sdl->common(), FALSE, PTR_FLAGS_MOVE, x, y);
}
/* This function is called to update the keyboard indicator LED */
BOOL sdlInput::keyboard_set_indicators(rdpContext* context, UINT16 led_flags)
{
WINPR_UNUSED(context);
int 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(static_cast<SDL_Keymod>(state));
return TRUE;
}
/* This function is called to set the IME state */
BOOL sdlInput::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 std::map<std::string, uint32_t>& getSdlMap()
{
static std::map<std::string, uint32_t> s_map = {
{ "KMOD_LSHIFT", KMOD_LSHIFT }, { "KMOD_RSHIFT", KMOD_RSHIFT },
{ "KMOD_LCTRL", KMOD_LCTRL }, { "KMOD_RCTRL", KMOD_RCTRL },
{ "KMOD_LALT", KMOD_LALT }, { "KMOD_RALT", KMOD_RALT },
{ "KMOD_LGUI", KMOD_LGUI }, { "KMOD_RGUI", KMOD_RGUI },
{ "KMOD_NUM", KMOD_NUM }, { "KMOD_CAPS", KMOD_CAPS },
{ "KMOD_MODE", KMOD_MODE },
#if SDL_VERSION_ATLEAST(2, 0, 18)
{ "KMOD_SCROLL", KMOD_SCROLL },
#endif
{ "KMOD_CTRL", KMOD_CTRL }, { "KMOD_SHIFT", KMOD_SHIFT },
{ "KMOD_ALT", KMOD_ALT }, { "KMOD_GUI", KMOD_GUI },
{ "KMOD_NONE", KMOD_NONE }, { "SDL_KMOD_LSHIFT", KMOD_LSHIFT },
{ "SDL_KMOD_RSHIFT", KMOD_RSHIFT }, { "SDL_KMOD_LCTRL", KMOD_LCTRL },
{ "SDL_KMOD_RCTRL", KMOD_RCTRL }, { "SDL_KMOD_LALT", KMOD_LALT },
{ "SDL_KMOD_RALT", KMOD_RALT }, { "SDL_KMOD_LGUI", KMOD_LGUI },
{ "SDL_KMOD_RGUI", KMOD_RGUI }, { "SDL_KMOD_NUM", KMOD_NUM },
{ "SDL_KMOD_CAPS", KMOD_CAPS }, { "SDL_KMOD_MODE", KMOD_MODE },
{ "SDL_KMOD_SCROLL", KMOD_SCROLL }, { "SDL_KMOD_CTRL", KMOD_CTRL },
{ "SDL_KMOD_SHIFT", KMOD_SHIFT }, { "SDL_KMOD_ALT", KMOD_ALT },
{ "SDL_KMOD_GUI", KMOD_GUI }, { "SDL_KMOD_NONE", KMOD_NONE }
};
return s_map;
}
bool sdlInput::prefToEnabled()
{
bool enable = true;
const auto& m = getSdlMap();
for (const auto& val : SdlPref::instance()->get_array("SDL_KeyModMask", { "KMOD_RSHIFT" }))
{
auto it = m.find(val);
if (it != m.end())
{
if (it->second == KMOD_NONE)
enable = false;
}
else
{
WLog_WARN(TAG, "Invalid config::SDL_KeyModMask entry value '%s', disabling hotkeys",
val.c_str());
enable = false;
}
}
return enable;
}
uint32_t sdlInput::prefToMask()
{
const auto& mapping = getSdlMap();
uint32_t mod = KMOD_NONE;
for (const auto& val : SdlPref::instance()->get_array("SDL_KeyModMask", { "KMOD_RSHIFT" }))
{
auto it = mapping.find(val);
if (it != mapping.end())
mod |= it->second;
}
return mod;
}
static Uint32 sdl_scancode_val(const char* scancodeName)
{
for (const auto& cur : map)
{
if (strcmp(cur.sdl_name, scancodeName) == 0)
return cur.sdl;
}
return SDL_SCANCODE_UNKNOWN;
}
static UINT32 sdl_scancode_to_rdp(Uint32 scancode)
{
UINT32 rdp = RDP_SCANCODE_UNKNOWN;
for (const auto& cur : map)
{
if (cur.sdl == scancode)
{
rdp = cur.rdp;
break;
}
}
#if defined(WITH_DEBUG_SDL_KBD_EVENTS)
auto code = static_cast<SDL_Scancode>(scancode);
WLog_DBG(TAG, "got %s [0x%08" PRIx32 "] -> [%s]", SDL_GetScancodeName(code), scancode,
freerdp_keyboard_scancode_name(rdp));
#endif
return rdp;
}
uint32_t sdlInput::prefKeyValue(const std::string& key, uint32_t fallback)
{
auto item = SdlPref::instance()->get_string(key);
if (item.empty())
return fallback;
auto val = sdl_scancode_val(item.c_str());
if (val == SDL_SCANCODE_UNKNOWN)
return fallback;
return val;
}
std::list<std::string> sdlInput::tokenize(const std::string& data, const std::string& delimiter)
{
size_t lastpos = 0;
size_t pos = 0;
std::list<std::string> list;
while ((pos = data.find(delimiter, lastpos)) != std::string::npos)
{
auto token = data.substr(lastpos, pos);
lastpos = pos + 1;
list.push_back(std::move(token));
}
auto token = data.substr(lastpos);
list.push_back(std::move(token));
return list;
}
bool sdlInput::extract(const std::string& token, uint32_t& key, uint32_t& value)
{
return freerdp_extract_key_value(token.c_str(), &key, &value);
}
BOOL sdlInput::keyboard_handle_event(const SDL_KeyboardEvent* ev)
{
WINPR_ASSERT(ev);
const UINT32 rdp_scancode = sdl_scancode_to_rdp(ev->keysym.scancode);
const SDL_Keymod mods = SDL_GetModState();
if (_hotkeysEnabled && (mods & _hotkeyModmask) == _hotkeyModmask)
{
if (ev->type == SDL_KEYDOWN)
{
if (ev->keysym.scancode == _hotkeyFullscreen)
{
_sdl->update_fullscreen(!_sdl->fullscreen);
return TRUE;
}
if (ev->keysym.scancode == _hotkeyResizable)
{
_sdl->update_resizeable(!_sdl->resizeable);
return TRUE;
}
if (ev->keysym.scancode == _hotkeyGrab)
{
keyboard_grab(ev->windowID, !_sdl->grab_kbd);
return TRUE;
}
if (ev->keysym.scancode == _hotkeyDisconnect)
{
freerdp_abort_connect_context(_sdl->context());
return TRUE;
}
if (ev->keysym.scancode == _hotkeyMinimize)
{
_sdl->update_minimize();
return TRUE;
}
}
}
auto scancode = freerdp_keyboard_remap_key(_remapTable, rdp_scancode);
return freerdp_input_send_keyboard_event_ex(_sdl->context()->input, ev->type == SDL_KEYDOWN,
ev->repeat, scancode);
}
BOOL sdlInput::keyboard_grab(Uint32 windowID, bool enable)
{
auto it = _sdl->windows.find(windowID);
if (it == _sdl->windows.end())
return FALSE;
auto status = enable && _sdl->grab_kbd_enabled;
_sdl->grab_kbd = status;
return it->second.grabKeyboard(status);
}
BOOL sdlInput::mouse_focus(Uint32 windowID)
{
if (_lastWindowID != windowID)
{
_lastWindowID = windowID;
auto it = _sdl->windows.find(windowID);
if (it == _sdl->windows.end())
return FALSE;
it->second.raise();
}
return TRUE;
}
BOOL sdlInput::mouse_grab(Uint32 windowID, SDL_bool enable)
{
auto it = _sdl->windows.find(windowID);
if (it == _sdl->windows.end())
return FALSE;
_sdl->grab_mouse = enable;
return it->second.grabMouse(enable);
}
sdlInput::sdlInput(SdlContext* sdl)
: _sdl(sdl), _lastWindowID(UINT32_MAX), _hotkeysEnabled(prefToEnabled()),
_hotkeyModmask(prefToMask())
{
auto list =
freerdp_settings_get_string(_sdl->context()->settings, FreeRDP_KeyboardRemappingList);
_remapTable = freerdp_keyboard_remap_string_to_list(list);
assert(_remapTable);
_hotkeyFullscreen = prefKeyValue("SDL_Fullscreen", SDL_SCANCODE_RETURN);
_hotkeyResizable = prefKeyValue("SDL_Resizeable", SDL_SCANCODE_R);
_hotkeyGrab = prefKeyValue("SDL_Grab", SDL_SCANCODE_G);
_hotkeyDisconnect = prefKeyValue("SDL_Disconnect", SDL_SCANCODE_D);
_hotkeyMinimize = prefKeyValue("SDL_Minimize", SDL_SCANCODE_M);
}
sdlInput::~sdlInput()
{
freerdp_keyboard_remap_free(_remapTable);
}

View File

@@ -0,0 +1,79 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client keyboard helper
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <string>
#include <map>
#include <list>
#include <atomic>
#include <winpr/wtypes.h>
#include <freerdp/freerdp.h>
#include <freerdp/locale/keyboard.h>
#include <SDL.h>
#include "sdl_types.hpp"
class sdlInput
{
public:
explicit sdlInput(SdlContext* sdl);
sdlInput(const sdlInput& other) = delete;
sdlInput(sdlInput&& other) = delete;
~sdlInput();
sdlInput& operator=(const sdlInput& other) = delete;
sdlInput& operator=(sdlInput&& other) = delete;
BOOL keyboard_sync_state();
BOOL keyboard_focus_in();
BOOL keyboard_handle_event(const SDL_KeyboardEvent* ev);
BOOL keyboard_grab(Uint32 windowID, bool enable);
BOOL mouse_focus(Uint32 windowID);
BOOL mouse_grab(Uint32 windowID, SDL_bool enable);
static BOOL keyboard_set_indicators(rdpContext* context, UINT16 led_flags);
static BOOL keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
UINT32 imeConvMode);
static bool prefToEnabled();
static uint32_t prefToMask();
static uint32_t prefKeyValue(const std::string& key, uint32_t fallback = SDL_SCANCODE_UNKNOWN);
private:
static std::list<std::string> tokenize(const std::string& data,
const std::string& delimiter = ",");
static bool extract(const std::string& token, uint32_t& key, uint32_t& value);
SdlContext* _sdl;
Uint32 _lastWindowID;
// hotkey handling
bool _hotkeysEnabled;
uint32_t _hotkeyModmask; // modifier keys mask
uint32_t _hotkeyFullscreen;
uint32_t _hotkeyResizable;
uint32_t _hotkeyGrab;
uint32_t _hotkeyDisconnect;
uint32_t _hotkeyMinimize;
FREERDP_REMAP_TABLE* _remapTable;
};

View File

@@ -0,0 +1,393 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* X11 Monitor Handling
*
* Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2017 David Fort <contact@hardening-consulting.com>
* Copyright 2018 Kai Harms <kharms@rangee.com>
*
* 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 <freerdp/config.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <SDL.h>
#include <winpr/assert.h>
#include <winpr/crt.h>
#include <freerdp/log.h>
#define TAG CLIENT_TAG("sdl")
#include "sdl_monitor.hpp"
#include "sdl_freerdp.hpp"
using MONITOR_INFO = struct
{
RECTANGLE_16 area;
RECTANGLE_16 workarea;
BOOL primary;
};
using VIRTUAL_SCREEN = struct
{
int nmonitors;
RECTANGLE_16 area;
RECTANGLE_16 workarea;
MONITOR_INFO* monitors;
};
/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071
*/
int sdl_list_monitors([[maybe_unused]] SdlContext* sdl)
{
SDL_Init(SDL_INIT_VIDEO);
const int nmonitors = SDL_GetNumVideoDisplays();
printf("listing %d monitors:\n", nmonitors);
for (int i = 0; i < nmonitors; i++)
{
SDL_Rect rect = {};
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_apply_max_size(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
{
WINPR_ASSERT(sdl);
WINPR_ASSERT(pMaxWidth);
WINPR_ASSERT(pMaxHeight);
auto settings = sdl->context()->settings;
WINPR_ASSERT(settings);
*pMaxWidth = 0;
*pMaxHeight = 0;
for (size_t x = 0; x < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); x++)
{
auto monitor = static_cast<const rdpMonitor*>(
freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
{
*pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width);
*pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height);
}
else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
{
SDL_Rect rect = {};
SDL_GetDisplayUsableBounds(WINPR_ASSERTING_INT_CAST(int, monitor->orig_screen), &rect);
*pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w);
*pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h);
}
else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) > 0)
{
SDL_Rect rect = {};
SDL_GetDisplayUsableBounds(WINPR_ASSERTING_INT_CAST(int, monitor->orig_screen), &rect);
*pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w);
*pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h);
if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
*pMaxWidth = (WINPR_ASSERTING_INT_CAST(uint32_t, rect.w) *
freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
100;
if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
*pMaxHeight = (WINPR_ASSERTING_INT_CAST(uint32_t, rect.h) *
freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
100;
}
else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
{
*pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
*pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_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_LANDSCAPE_FLIPPED;
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
return ORIENTATION_PORTRAIT_FLIPPED;
case SDL_ORIENTATION_PORTRAIT:
default:
return ORIENTATION_PORTRAIT;
}
}
#endif
static Uint32 scale(Uint32 val, float scale)
{
const auto dval = static_cast<float>(val);
const auto sval = dval / scale;
return static_cast<Uint32>(sval);
}
static BOOL sdl_apply_display_properties(SdlContext* sdl)
{
WINPR_ASSERT(sdl);
rdpSettings* settings = sdl->context()->settings;
WINPR_ASSERT(settings);
if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
return TRUE;
const UINT32 numIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
std::vector<rdpMonitor> monitors;
for (UINT32 x = 0; x < numIds; x++)
{
auto id = static_cast<const int*>(
freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x));
WINPR_ASSERT(id);
float ddpi = 1.0f;
float hdpi = 1.0f;
float vdpi = 1.0f;
SDL_Rect rect = {};
if (SDL_GetDisplayBounds(*id, &rect) < 0)
return FALSE;
if (SDL_GetDisplayDPI(*id, &ddpi, &hdpi, &vdpi) < 0)
return FALSE;
WINPR_ASSERT(rect.w > 0);
WINPR_ASSERT(rect.h > 0);
WINPR_ASSERT(ddpi > 0);
WINPR_ASSERT(hdpi > 0);
WINPR_ASSERT(vdpi > 0);
bool highDpi = hdpi > 100;
if (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 = {};
SDL_GetDisplayMode(WINPR_ASSERTING_INT_CAST(int, 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 = 1.0f * static_cast<float>(rect.w) / static_cast<float>(scaleRect.w);
const float dh = 1.0f * static_cast<float>(rect.h) / static_cast<float>(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 = {};
/* windows uses 96 dpi as 'default' and the scale factors are in percent. */
const auto factor = ddpi / 96.0f * 100.0f;
monitor.orig_screen = x;
monitor.x = rect.x;
monitor.y = rect.y;
monitor.width = rect.w;
monitor.height = rect.h;
monitor.is_primary = x == 0;
monitor.attributes.desktopScaleFactor = static_cast<UINT32>(factor);
monitor.attributes.deviceScaleFactor = 100;
monitor.attributes.orientation = rdp_orientation;
monitor.attributes.physicalWidth = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.w), hdpi);
monitor.attributes.physicalHeight = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.h), vdpi);
monitors.emplace_back(monitor);
}
return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(),
monitors.size());
}
static BOOL sdl_detect_single_window(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
{
WINPR_ASSERT(sdl);
WINPR_ASSERT(pMaxWidth);
WINPR_ASSERT(pMaxHeight);
rdpSettings* settings = sdl->context()->settings;
WINPR_ASSERT(settings);
if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
!freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
(freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
!freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
{
/* If no monitors were specified on the command-line then set the current monitor as active
*/
if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
{
const size_t id = (!sdl->windows.empty())
? WINPR_ASSERTING_INT_CAST(
uint32_t, sdl->windows.begin()->second.displayIndex())
: 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.
*/
if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, 1))
return FALSE;
}
// 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->context()->settings;
WINPR_ASSERT(settings);
const int numDisplays = SDL_GetNumVideoDisplays();
auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
if (numDisplays < 0)
return FALSE;
if (nr == 0)
{
if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, nullptr,
static_cast<size_t>(numDisplays)))
return FALSE;
for (size_t x = 0; x < static_cast<size_t>(numDisplays); x++)
{
if (!freerdp_settings_set_pointer_array(settings, FreeRDP_MonitorIds, x, &x))
return FALSE;
}
}
else
{
/* There were more IDs supplied than there are monitors */
if (nr > static_cast<UINT32>(numDisplays))
{
WLog_ERR(TAG, "Found %" PRIu32 " monitor IDs, but only have %d monitors connected", nr,
numDisplays);
return FALSE;
}
std::vector<UINT32> used;
for (size_t x = 0; x < nr; x++)
{
auto cur = static_cast<const UINT32*>(
freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x));
WINPR_ASSERT(cur);
auto id = *cur;
/* the ID is no valid monitor index */
if (id >= nr)
{
WLog_ERR(TAG,
"Supplied monitor ID[%" PRIuz "]=%" PRIu32 " is invalid, only [0-%" PRIu32
"] are allowed",
x, id, nr - 1);
return FALSE;
}
/* The ID is already taken */
if (std::find(used.begin(), used.end(), id) != used.end())
{
WLog_ERR(TAG, "Duplicate monitor ID[%" PRIuz "]=%" PRIu32 " detected", x, id);
return FALSE;
}
used.push_back(*cur);
}
}
if (!sdl_apply_display_properties(sdl))
return FALSE;
return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight);
}
INT64 sdl_monitor_id_for_index(SdlContext* sdl, UINT32 index)
{
WINPR_ASSERT(sdl);
auto settings = sdl->context()->settings;
auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
if (nr == 0)
return index;
if (nr <= index)
return -1;
auto cur = static_cast<const UINT32*>(
freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index));
WINPR_ASSERT(cur);
return *cur;
}

View File

@@ -0,0 +1,29 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Monitor Handling
*
* Copyright 2023 Armin Novak <anovak@thincast.com>
*
* 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.
*/
#pragma once
#include <freerdp/api.h>
#include <freerdp/freerdp.h>
#include "sdl_types.hpp"
int sdl_list_monitors(SdlContext* sdl);
BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight);
INT64 sdl_monitor_id_for_index(SdlContext* sdl, UINT32 index);

View File

@@ -0,0 +1,210 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Wayland Mouse Pointer
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 <freerdp/config.h>
#include <freerdp/gdi/gdi.h>
#include "sdl_pointer.hpp"
#include "sdl_freerdp.hpp"
#include "sdl_touch.hpp"
#include "sdl_utils.hpp"
#include <SDL_mouse.h>
using sdlPointer = struct
{
rdpPointer pointer;
SDL_Cursor* cursor;
SDL_Surface* image;
size_t size;
void* data;
};
static BOOL sdl_Pointer_New(rdpContext* context, rdpPointer* pointer)
{
auto ptr = reinterpret_cast<sdlPointer*>(pointer);
WINPR_ASSERT(context);
if (!ptr)
return FALSE;
rdpGdi* gdi = context->gdi;
WINPR_ASSERT(gdi);
ptr->size = 4ull * pointer->width * pointer->height;
ptr->data = winpr_aligned_malloc(ptr->size, 16);
if (!ptr->data)
return FALSE;
auto data = static_cast<BYTE*>(ptr->data);
if (!freerdp_image_copy_from_pointer_data(
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);
ptr->data = nullptr;
return FALSE;
}
return TRUE;
}
static void sdl_Pointer_Clear(sdlPointer* ptr)
{
WINPR_ASSERT(ptr);
SDL_FreeCursor(ptr->cursor);
SDL_FreeSurface(ptr->image);
ptr->cursor = nullptr;
ptr->image = nullptr;
}
static void sdl_Pointer_Free(rdpContext* context, rdpPointer* pointer)
{
auto ptr = reinterpret_cast<sdlPointer*>(pointer);
WINPR_UNUSED(context);
if (ptr)
{
sdl_Pointer_Clear(ptr);
winpr_aligned_free(ptr->data);
ptr->data = nullptr;
}
}
static BOOL sdl_Pointer_SetDefault(rdpContext* context)
{
WINPR_UNUSED(context);
return sdl_push_user_event(SDL_USEREVENT_POINTER_DEFAULT);
}
static BOOL sdl_Pointer_Set(rdpContext* context, rdpPointer* pointer)
{
auto sdl = get_context(context);
return sdl_push_user_event(SDL_USEREVENT_POINTER_SET, pointer, sdl);
}
BOOL sdl_Pointer_Set_Process(SDL_UserEvent* uptr)
{
INT32 w = 0;
INT32 h = 0;
INT32 x = 0;
INT32 y = 0;
INT32 sw = 0;
INT32 sh = 0;
WINPR_ASSERT(uptr);
auto sdl = static_cast<SdlContext*>(uptr->data2);
WINPR_ASSERT(sdl);
auto context = sdl->context();
auto ptr = static_cast<sdlPointer*>(uptr->data1);
WINPR_ASSERT(ptr);
rdpPointer* pointer = &ptr->pointer;
rdpGdi* gdi = context->gdi;
WINPR_ASSERT(gdi);
x = static_cast<INT32>(pointer->xPos);
y = static_cast<INT32>(pointer->yPos);
sw = w = static_cast<INT32>(pointer->width);
sh = h = static_cast<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, static_cast<int>(bpp), sdl->sdl_pixel_format);
if (!ptr->image)
return FALSE;
SDL_LockSurface(ptr->image);
auto pixels = static_cast<BYTE*>(ptr->image->pixels);
auto data = static_cast<const BYTE*>(ptr->data);
const BOOL rc = freerdp_image_scale(
pixels, gdi->dstFormat, static_cast<UINT32>(ptr->image->pitch), 0, 0,
static_cast<UINT32>(ptr->image->w), static_cast<UINT32>(ptr->image->h), data,
gdi->dstFormat, 0, 0, 0, static_cast<UINT32>(w), static_cast<UINT32>(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);
return sdl_push_user_event(SDL_USEREVENT_POINTER_NULL);
}
static BOOL sdl_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
{
WINPR_UNUSED(context);
WINPR_ASSERT(context);
return sdl_push_user_event(SDL_USEREVENT_POINTER_POSITION, x, y);
}
BOOL sdl_register_pointer(rdpGraphics* graphics)
{
const rdpPointer pointer = { sizeof(sdlPointer),
sdl_Pointer_New,
sdl_Pointer_Free,
sdl_Pointer_Set,
sdl_Pointer_SetNull,
sdl_Pointer_SetDefault,
sdl_Pointer_SetPosition,
{},
0,
0,
0,
0,
0,
0,
0,
nullptr,
nullptr,
{} };
graphics_register_pointer(graphics, &pointer);
return TRUE;
}

View File

@@ -0,0 +1,27 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Mouse Pointer
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <SDL.h>
#include <freerdp/graphics.h>
BOOL sdl_register_pointer(rdpGraphics* graphics);
BOOL sdl_Pointer_Set_Process(SDL_UserEvent* uptr);

View File

@@ -0,0 +1,283 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP SDL touch/mouse input
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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 <freerdp/config.h>
#include "sdl_touch.hpp"
#include "sdl_freerdp.hpp"
#include <winpr/wtypes.h>
#include <winpr/assert.h>
#include <freerdp/freerdp.h>
#include <freerdp/gdi/gdi.h>
#include <SDL.h>
BOOL sdl_scale_coordinates(SdlContext* sdl, Uint32 windowId, INT32* px, INT32* py,
BOOL fromLocalToRDP, BOOL applyOffset)
{
rdpGdi* gdi = nullptr;
double sx = 1.0;
double sy = 1.0;
if (!sdl || !px || !py || !sdl->context()->gdi)
return FALSE;
WINPR_ASSERT(sdl->context()->gdi);
WINPR_ASSERT(sdl->context()->settings);
gdi = sdl->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 (const auto& it : sdl->windows)
{
auto& window = it.second;
const auto id = window.id();
if (id != windowId)
{
continue;
}
auto size = window.rect();
sx = size.w / static_cast<double>(gdi->width);
sy = size.h / static_cast<double>(gdi->height);
offset_x = window.offsetX();
offset_y = window.offsetY();
break;
}
if (freerdp_settings_get_bool(sdl->context()->settings, FreeRDP_SmartSizing))
{
if (!fromLocalToRDP)
{
*px = static_cast<INT32>(*px * sx);
*py = static_cast<INT32>(*py * sy);
}
else
{
*px = static_cast<INT32>(*px / sx);
*py = static_cast<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 = 0;
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 = static_cast<INT32>(ev->x * static_cast<float>(surface->w));
*py = static_cast<INT32>(ev->y * static_cast<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 : static_cast<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 static_cast<UINT32>(val);
}
BOOL sdl_handle_touch_up(SdlContext* sdl, const SDL_TouchFingerEvent* ev)
{
WINPR_ASSERT(sdl);
WINPR_ASSERT(ev);
INT32 x = 0;
INT32 y = 0;
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,
static_cast<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 = 0;
INT32 y = 0;
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,
static_cast<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 = 0;
INT32 y = 0;
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,
static_cast<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);
sdl->input.mouse_focus(ev->windowID);
const BOOL relative = freerdp_client_use_relative_mouse_events(sdl->common());
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) * 0x78;
const INT32 y = ev->y * (flipped ? -1 : 1) * 0x78;
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;
}
const BOOL relative = freerdp_client_use_relative_mouse_events(sdl->common());
INT32 x = relative ? 0 : ev->x;
INT32 y = relative ? 0 : 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(), relative, flags, x, y);
else if ((xflags & (~PTR_XFLAGS_DOWN)) != 0)
return freerdp_client_send_extended_button_event(sdl->common(), relative, xflags, x, y);
else
return FALSE;
}

View File

@@ -0,0 +1,36 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP SDL touch/mouse input
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <winpr/wtypes.h>
#include <SDL.h>
#include "sdl_types.hpp"
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);

View File

@@ -0,0 +1,46 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <freerdp/freerdp.h>
class SdlContext;
typedef struct
{
rdpClientContext common;
SdlContext* sdl;
} sdl_rdp_context;
static inline SdlContext* get_context(void* ctx)
{
if (!ctx)
return nullptr;
auto sdl = static_cast<sdl_rdp_context*>(ctx);
return sdl->sdl;
}
static inline SdlContext* get_context(rdpContext* ctx)
{
if (!ctx)
return nullptr;
auto sdl = reinterpret_cast<sdl_rdp_context*>(ctx);
return sdl->sdl;
}

View File

@@ -0,0 +1,293 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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 <cassert>
#include "sdl_utils.hpp"
#include "sdl_freerdp.hpp"
#include <SDL.h>
#include <freerdp/version.h>
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_USEREVENT_CERT_DIALOG);
EV_CASE_STR(SDL_USEREVENT_CERT_RESULT);
EV_CASE_STR(SDL_USEREVENT_SHOW_DIALOG);
EV_CASE_STR(SDL_USEREVENT_SHOW_RESULT);
EV_CASE_STR(SDL_USEREVENT_AUTH_DIALOG);
EV_CASE_STR(SDL_USEREVENT_AUTH_RESULT);
EV_CASE_STR(SDL_USEREVENT_SCARD_DIALOG);
EV_CASE_STR(SDL_USEREVENT_RETRY_DIALOG);
EV_CASE_STR(SDL_USEREVENT_SCARD_RESULT);
EV_CASE_STR(SDL_USEREVENT_UPDATE);
EV_CASE_STR(SDL_USEREVENT_CREATE_WINDOWS);
EV_CASE_STR(SDL_USEREVENT_WINDOW_RESIZEABLE);
EV_CASE_STR(SDL_USEREVENT_WINDOW_FULLSCREEN);
EV_CASE_STR(SDL_USEREVENT_WINDOW_MINIMIZE);
EV_CASE_STR(SDL_USEREVENT_POINTER_NULL);
EV_CASE_STR(SDL_USEREVENT_POINTER_DEFAULT);
EV_CASE_STR(SDL_USEREVENT_POINTER_POSITION);
EV_CASE_STR(SDL_USEREVENT_POINTER_SET);
EV_CASE_STR(SDL_USEREVENT_QUIT);
EV_CASE_STR(SDL_LASTEVENT);
default:
return "SDL_UNKNOWNEVENT";
}
#undef EV_CASE_STR
#undef STR
}
const char* sdl_error_string(Sint32 res)
{
if (res == 0)
return nullptr;
return SDL_GetError();
}
BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line,
const char* fkt)
{
const char* msg = sdl_error_string(res);
WINPR_UNUSED(file);
if (!msg)
return FALSE;
WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, msg);
return TRUE;
}
BOOL sdl_push_user_event(Uint32 type, ...)
{
SDL_Event ev = {};
SDL_UserEvent* event = &ev.user;
va_list ap = {};
va_start(ap, type);
event->type = type;
switch (type)
{
case SDL_USEREVENT_AUTH_RESULT:
{
auto arg = reinterpret_cast<SDL_UserAuthArg*>(ev.padding);
arg->user = va_arg(ap, char*);
arg->domain = va_arg(ap, char*);
arg->password = va_arg(ap, char*);
arg->result = va_arg(ap, Sint32);
}
break;
case SDL_USEREVENT_AUTH_DIALOG:
{
auto arg = reinterpret_cast<SDL_UserAuthArg*>(ev.padding);
arg->title = va_arg(ap, char*);
arg->user = va_arg(ap, char*);
arg->domain = va_arg(ap, char*);
arg->password = va_arg(ap, char*);
arg->result = va_arg(ap, Sint32);
}
break;
case SDL_USEREVENT_SCARD_DIALOG:
{
event->data1 = va_arg(ap, char*);
event->data2 = va_arg(ap, char**);
event->code = va_arg(ap, Sint32);
}
break;
case SDL_USEREVENT_RETRY_DIALOG:
break;
case SDL_USEREVENT_SCARD_RESULT:
case SDL_USEREVENT_SHOW_RESULT:
case SDL_USEREVENT_CERT_RESULT:
event->code = va_arg(ap, Sint32);
break;
case SDL_USEREVENT_SHOW_DIALOG:
event->data1 = va_arg(ap, char*);
event->data2 = va_arg(ap, char*);
event->code = va_arg(ap, Sint32);
break;
case SDL_USEREVENT_CERT_DIALOG:
event->data1 = va_arg(ap, char*);
event->data2 = va_arg(ap, char*);
break;
case SDL_USEREVENT_UPDATE:
event->data1 = va_arg(ap, void*);
break;
case SDL_USEREVENT_POINTER_POSITION:
event->data1 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, UINT32)));
event->data2 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, UINT32)));
break;
case SDL_USEREVENT_POINTER_SET:
event->data1 = va_arg(ap, void*);
event->data2 = va_arg(ap, void*);
break;
case SDL_USEREVENT_CREATE_WINDOWS:
event->data1 = va_arg(ap, void*);
break;
case SDL_USEREVENT_WINDOW_FULLSCREEN:
case SDL_USEREVENT_WINDOW_RESIZEABLE:
event->data1 = va_arg(ap, void*);
event->code = va_arg(ap, int);
break;
case SDL_USEREVENT_WINDOW_MINIMIZE:
case SDL_USEREVENT_QUIT:
case SDL_USEREVENT_POINTER_NULL:
case SDL_USEREVENT_POINTER_DEFAULT:
break;
default:
va_end(ap);
return FALSE;
}
va_end(ap);
return SDL_PushEvent(&ev) == 1;
}
bool sdl_push_quit()
{
SDL_Event ev = {};
ev.type = SDL_QUIT;
SDL_PushEvent(&ev);
return true;
}
std::string sdl_window_event_str(Uint8 ev)
{
switch (ev)
{
case SDL_WINDOWEVENT_NONE:
return "SDL_WINDOWEVENT_NONE";
case SDL_WINDOWEVENT_SHOWN:
return "SDL_WINDOWEVENT_SHOWN";
case SDL_WINDOWEVENT_HIDDEN:
return "SDL_WINDOWEVENT_HIDDEN";
case SDL_WINDOWEVENT_EXPOSED:
return "SDL_WINDOWEVENT_EXPOSED";
case SDL_WINDOWEVENT_MOVED:
return "SDL_WINDOWEVENT_MOVED";
case SDL_WINDOWEVENT_RESIZED:
return "SDL_WINDOWEVENT_RESIZED";
case SDL_WINDOWEVENT_SIZE_CHANGED:
return "SDL_WINDOWEVENT_SIZE_CHANGED";
case SDL_WINDOWEVENT_MINIMIZED:
return "SDL_WINDOWEVENT_MINIMIZED";
case SDL_WINDOWEVENT_MAXIMIZED:
return "SDL_WINDOWEVENT_MAXIMIZED";
case SDL_WINDOWEVENT_RESTORED:
return "SDL_WINDOWEVENT_RESTORED";
case SDL_WINDOWEVENT_ENTER:
return "SDL_WINDOWEVENT_ENTER";
case SDL_WINDOWEVENT_LEAVE:
return "SDL_WINDOWEVENT_LEAVE";
case SDL_WINDOWEVENT_FOCUS_GAINED:
return "SDL_WINDOWEVENT_FOCUS_GAINED";
case SDL_WINDOWEVENT_FOCUS_LOST:
return "SDL_WINDOWEVENT_FOCUS_LOST";
case SDL_WINDOWEVENT_CLOSE:
return "SDL_WINDOWEVENT_CLOSE";
#if SDL_VERSION_ATLEAST(2, 0, 5)
case SDL_WINDOWEVENT_TAKE_FOCUS:
return "SDL_WINDOWEVENT_TAKE_FOCUS";
case SDL_WINDOWEVENT_HIT_TEST:
return "SDL_WINDOWEVENT_HIT_TEST";
#endif
#if SDL_VERSION_ATLEAST(2, 0, 18)
case SDL_WINDOWEVENT_ICCPROF_CHANGED:
return "SDL_WINDOWEVENT_ICCPROF_CHANGED";
case SDL_WINDOWEVENT_DISPLAY_CHANGED:
return "SDL_WINDOWEVENT_DISPLAY_CHANGED";
#endif
default:
return "SDL_WINDOWEVENT_UNKNOWN";
}
}

View File

@@ -0,0 +1,76 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client
*
* Copyright 2022 Armin Novak <armin.novak@thincast.com>
*
* 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.
*/
#pragma once
#include <winpr/synch.h>
#include <winpr/wlog.h>
#include <SDL.h>
#include <string>
#include <vector>
#include <sdl_common_utils.hpp>
enum
{
SDL_USEREVENT_UPDATE = SDL_USEREVENT + 1,
SDL_USEREVENT_CREATE_WINDOWS,
SDL_USEREVENT_WINDOW_RESIZEABLE,
SDL_USEREVENT_WINDOW_FULLSCREEN,
SDL_USEREVENT_WINDOW_MINIMIZE,
SDL_USEREVENT_POINTER_NULL,
SDL_USEREVENT_POINTER_DEFAULT,
SDL_USEREVENT_POINTER_POSITION,
SDL_USEREVENT_POINTER_SET,
SDL_USEREVENT_QUIT,
SDL_USEREVENT_CERT_DIALOG,
SDL_USEREVENT_SHOW_DIALOG,
SDL_USEREVENT_AUTH_DIALOG,
SDL_USEREVENT_SCARD_DIALOG,
SDL_USEREVENT_RETRY_DIALOG,
SDL_USEREVENT_CERT_RESULT,
SDL_USEREVENT_SHOW_RESULT,
SDL_USEREVENT_AUTH_RESULT,
SDL_USEREVENT_SCARD_RESULT
};
typedef struct
{
Uint32 type;
Uint32 timestamp;
char* title;
char* user;
char* domain;
char* password;
Sint32 result;
} SDL_UserAuthArg;
BOOL sdl_push_user_event(Uint32 type, ...);
bool sdl_push_quit();
std::string sdl_window_event_str(Uint8 ev);
const char* sdl_event_type_str(Uint32 type);
const char* sdl_error_string(Sint32 res);
#define sdl_log_error(res, log, what) sdl_log_error_ex(res, log, what, __FILE__, __LINE__, __func__)
BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line,
const char* fkt);

View File

@@ -0,0 +1,207 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
* Copyright 2023 Thincast Technologies GmbH
*
* 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_window.hpp"
#include "sdl_utils.hpp"
SdlWindow::SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width,
Sint32 height, Uint32 flags)
: _window(SDL_CreateWindow(title.c_str(), startupX, startupY, width, height, flags))
{
}
SdlWindow::SdlWindow(SdlWindow&& other) noexcept
: _window(other._window), _offset_x(other._offset_x), _offset_y(other._offset_y)
{
other._window = nullptr;
}
SdlWindow::~SdlWindow()
{
SDL_DestroyWindow(_window);
}
Uint32 SdlWindow::id() const
{
if (!_window)
return 0;
return SDL_GetWindowID(_window);
}
int SdlWindow::displayIndex() const
{
if (!_window)
return 0;
return SDL_GetWindowDisplayIndex(_window);
}
SDL_Rect SdlWindow::rect() const
{
SDL_Rect rect = {};
if (_window)
{
SDL_GetWindowPosition(_window, &rect.x, &rect.y);
SDL_GetWindowSize(_window, &rect.w, &rect.h);
}
return rect;
}
SDL_Window* SdlWindow::window() const
{
return _window;
}
Sint32 SdlWindow::offsetX() const
{
return _offset_x;
}
void SdlWindow::setOffsetX(Sint32 x)
{
_offset_x = x;
}
void SdlWindow::setOffsetY(Sint32 y)
{
_offset_y = y;
}
Sint32 SdlWindow::offsetY() const
{
return _offset_y;
}
bool SdlWindow::grabKeyboard(bool enable)
{
if (!_window)
return false;
#if SDL_VERSION_ATLEAST(2, 0, 16)
SDL_SetWindowKeyboardGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
return true;
#else
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Keyboard grabbing not supported by SDL2 < 2.0.16");
return false;
#endif
}
bool SdlWindow::grabMouse(bool enable)
{
if (!_window)
return false;
#if SDL_VERSION_ATLEAST(2, 0, 16)
SDL_SetWindowMouseGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
#else
SDL_SetWindowGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
#endif
return true;
}
void SdlWindow::setBordered(bool bordered)
{
if (_window)
SDL_SetWindowBordered(_window, bordered ? SDL_TRUE : SDL_FALSE);
}
void SdlWindow::raise()
{
SDL_RaiseWindow(_window);
}
void SdlWindow::resizeable(bool use)
{
SDL_SetWindowResizable(_window, use ? SDL_TRUE : SDL_FALSE);
}
void SdlWindow::fullscreen(bool enter)
{
auto curFlags = SDL_GetWindowFlags(_window);
if (enter)
{
if (!(curFlags & SDL_WINDOW_BORDERLESS))
{
auto idx = SDL_GetWindowDisplayIndex(_window);
SDL_DisplayMode mode = {};
SDL_GetCurrentDisplayMode(idx, &mode);
SDL_RestoreWindow(_window); // Maximize so we can see the caption and
// bits
SDL_SetWindowBordered(_window, SDL_FALSE);
SDL_SetWindowPosition(_window, 0, 0);
#if SDL_VERSION_ATLEAST(2, 0, 16)
SDL_SetWindowAlwaysOnTop(_window, SDL_TRUE);
#endif
SDL_RaiseWindow(_window);
SDL_SetWindowSize(_window, mode.w, mode.h);
}
}
else
{
if (curFlags & SDL_WINDOW_BORDERLESS)
{
SDL_SetWindowBordered(_window, SDL_TRUE);
#if SDL_VERSION_ATLEAST(2, 0, 16)
SDL_SetWindowAlwaysOnTop(_window, SDL_FALSE);
#endif
SDL_RaiseWindow(_window);
SDL_MinimizeWindow(_window); // Maximize so we can see the caption and bits
SDL_MaximizeWindow(_window); // Maximize so we can see the caption and bits
}
}
}
void SdlWindow::minimize()
{
SDL_MinimizeWindow(_window);
}
bool SdlWindow::fill(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
{
auto surface = SDL_GetWindowSurface(_window);
if (!surface)
return false;
SDL_Rect rect = { 0, 0, surface->w, surface->h };
auto color = SDL_MapRGBA(surface->format, r, g, b, a);
SDL_FillRect(surface, &rect, color);
return true;
}
bool SdlWindow::blit(SDL_Surface* surface, SDL_Rect srcRect, SDL_Rect& dstRect)
{
auto screen = SDL_GetWindowSurface(_window);
if (!screen || !surface)
return false;
if (!SDL_SetClipRect(surface, &srcRect))
return true;
if (!SDL_SetClipRect(screen, &dstRect))
return true;
auto rc = SDL_BlitScaled(surface, &srcRect, screen, &dstRect);
if (rc != 0)
{
SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_BlitScaled: %s [%d]", sdl_error_string(rc), rc);
}
return rc == 0;
}
void SdlWindow::updateSurface()
{
SDL_UpdateWindowSurface(_window);
}

View File

@@ -0,0 +1,64 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
* Copyright 2023 Thincast Technologies GmbH
*
* 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.
*/
#pragma once
#include <string>
#include <SDL.h>
class SdlWindow
{
public:
SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width,
Sint32 height, Uint32 flags);
SdlWindow(const SdlWindow& other) = delete;
SdlWindow(SdlWindow&& other) noexcept;
~SdlWindow();
SdlWindow& operator=(const SdlWindow& other) = delete;
SdlWindow& operator=(SdlWindow&& other) = delete;
[[nodiscard]] Uint32 id() const;
[[nodiscard]] int displayIndex() const;
[[nodiscard]] SDL_Rect rect() const;
[[nodiscard]] SDL_Window* window() const;
[[nodiscard]] Sint32 offsetX() const;
void setOffsetX(Sint32 x);
void setOffsetY(Sint32 y);
[[nodiscard]] Sint32 offsetY() const;
bool grabKeyboard(bool enable);
bool grabMouse(bool enable);
void setBordered(bool bordered);
void raise();
void resizeable(bool use);
void fullscreen(bool enter);
void minimize();
bool fill(Uint8 r = 0x00, Uint8 g = 0x00, Uint8 b = 0x00, Uint8 a = 0xff);
bool blit(SDL_Surface* surface, SDL_Rect src, SDL_Rect& dst);
void updateSurface();
private:
SDL_Window* _window = nullptr;
Sint32 _offset_x = 0;
Sint32 _offset_y = 0;
};