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,104 @@
# 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.
cmake_minimum_required(VERSION 3.13)
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
if(NOT FREERDP_DEFAULT_PROJECT_VERSION)
set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
endif()
project(sdl-freerdp LANGUAGES CXX VERSION ${FREERDP_DEFAULT_PROJECT_VERSION})
message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/)
include(ProjectCXXStandard)
include(CommonConfigOptions)
include(ConfigureFreeRDP)
include(CXXCompilerFlags)
option(WITH_DEBUG_SDL_EVENTS "[dangerous, not for release builds!] Debug SDL events" ${DEFAULT_DEBUG_OPTION})
option(WITH_DEBUG_SDL_KBD_EVENTS "[dangerous, not for release builds!] Debug SDL keyboard events"
${DEFAULT_DEBUG_OPTION}
)
option(WITH_WIN_CONSOLE "Build ${PROJECT_NAME} with console support" ON)
option(WITH_SDL_LINK_SHARED "link SDL dynamic or static" ON)
if(WITH_WIN_CONSOLE)
set(WIN32_GUI_FLAG "TRUE")
else()
set(WIN32_GUI_FLAG "WIN32")
endif()
if(WITH_DEBUG_SDL_EVENTS)
add_compile_definitions(WITH_DEBUG_SDL_EVENTS)
endif()
if(WITH_DEBUG_SDL_KBD_EVENTS)
add_compile_definitions(WITH_DEBUG_SDL_KBD_EVENTS)
endif()
include(CMakeDependentOption)
find_package(SDL3)
cmake_dependent_option(WITH_CLIENT_SDL_VERSIONED "append sdl version to client binaries" OFF WITH_CLIENT_SDL OFF)
if(NOT WITHOUT_FREERDP_3x_DEPRECATED)
# Require 2.0.20 for ubuntu 22.04.
# older versions do not have the SDL2::SDL2 et al targets
find_package(SDL2 2.0.20)
cmake_dependent_option(
WITH_CLIENT_SDL2 "[deprecated,experimental] build deprecated,experimental SDL2 client" ${SDL2_FOUND}
WITH_CLIENT_SDL OFF
)
endif()
cmake_dependent_option(WITH_CLIENT_SDL3 "build SDL3 client" ${SDL3_FOUND} WITH_CLIENT_SDL OFF)
if(WITH_CLIENT_SDL2 AND WITH_CLIENT_SDL3)
message("Building both, SDL2 and SDL3 clients, forcing WITH_CLIENT_SDL_VERSIONED=ON")
set(WITH_CLIENT_SDL_VERSIONED ON)
endif()
if(NOT SDL2_FOUND AND NOT SDL3_FOUND)
message(WARNING "No SDL library detected, giving up. Install SDL2 or SDL3 development package to fix")
endif()
if((WITH_CLIENT_SDL2 AND SDL2_FOUND) OR (WITH_CLIENT_SDL3 AND SDL3_FOUND))
add_subdirectory(common)
include_directories(common)
endif()
if(NOT WITHOUT_FREERDP_3x_DEPRECATED)
if(WITH_CLIENT_SDL2)
if(SDL2_FOUND)
add_subdirectory(SDL2)
else()
message(WARNING "SDL2 requested but not found, continuing build without SDL2 client")
endif()
endif()
endif()
if(WITH_CLIENT_SDL3)
if(SDL3_FOUND)
add_subdirectory(SDL3)
else()
message(WARNING "SDL3 requested but not found, continuing build without SDL3 client")
endif()
endif()

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;
};

View File

@@ -0,0 +1,92 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP SDL Client
#
# Copyright 2022 Armin Novak <anovak@thincast.com>
# 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(MODULE_NAME "sdl3-freerdp")
find_package(SDL3 REQUIRED)
find_package(Threads REQUIRED)
add_subdirectory(dialogs)
set(SRCS
sdl_types.hpp
sdl_utils.cpp
sdl_utils.hpp
sdl_input.cpp
sdl_input.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
sdl_clip.hpp
sdl_clip.cpp
sdl_context.hpp
sdl_context.cpp
)
list(
APPEND
LIBS
winpr
freerdp
freerdp-client
Threads::Threads
sdl3_client_res
sdl3-dialogs
sdl-common-aad-view
sdl-common-prefs
)
if(NOT WITH_SDL_LINK_SHARED)
list(APPEND LIBS SDL3::SDL3-static)
else()
list(APPEND LIBS SDL3::SDL3)
endif()
set_target_properties(SDL3::Headers PROPERTIES SYSTEM TRUE)
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.sdl3")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sdl_config.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/sdl_config.hpp @ONLY)
installwithrpath(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,87 @@
# 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
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_widget_list.hpp
sdl_widget_list.cpp
sdl_input_widget.hpp
sdl_input_widget.cpp
sdl_input_widget_pair.hpp
sdl_input_widget_pair.cpp
sdl_input_widget_pair_list.hpp
sdl_input_widget_pair_list.cpp
sdl_select.hpp
sdl_select.cpp
sdl_select_list.hpp
sdl_select_list.cpp
sdl_selectable_widget.cpp
sdl_selectable_widget.hpp
sdl_connection_dialog.cpp
sdl_connection_dialog.hpp
sdl_connection_dialog_wrapper.cpp
sdl_connection_dialog_wrapper.hpp
sdl_blend_mode_guard.cpp
sdl_blend_mode_guard.hpp
)
list(APPEND LIBS sdl3_client_res winpr)
if(NOT WITH_SDL_LINK_SHARED)
list(APPEND LIBS ${SDL3_STATIC_LIBRARIES})
else()
list(APPEND LIBS ${SDL3_LIBRARIES})
endif()
macro(find_sdl_component name)
find_package(${name} REQUIRED)
if(WITH_SDL_LINK_SHARED)
list(APPEND LIBS ${name}::${name})
set_target_properties(${name}::${name}-shared PROPERTIES SYSTEM TRUE)
else()
list(APPEND LIBS ${name}::${name}-static)
set_target_properties(${name}::${name}-static PROPERTIES SYSTEM TRUE)
endif()
endmacro()
find_sdl_component(SDL3_ttf)
option(WITH_SDL_IMAGE_DIALOGS "Build with SDL_image support (recommended)" OFF)
if(WITH_SDL_IMAGE_DIALOGS)
find_sdl_component(SDL3_image)
add_compile_definitions(WITH_SDL_IMAGE_DIALOGS)
endif()
add_subdirectory(res)
add_library(sdl3-dialogs STATIC ${SRCS} sdl_connection_dialog_hider.hpp sdl_connection_dialog_hider.cpp)
set_property(TARGET sdl3-dialogs PROPERTY FOLDER "Client/SDL")
target_link_libraries(sdl3-dialogs PRIVATE ${LIBS})
option(SDL_DIALOG_TEST "Build dialog test binaries" OFF)
if(SDL_DIALOG_TEST)
add_subdirectory(test)
endif()

View File

@@ -0,0 +1,93 @@
Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,100 @@
Open Sans Variable Font
=======================
This download contains Open Sans as both variable fonts and static fonts.
Open Sans is a variable font with these axes:
wdth
wght
This means all the styles are contained in these files:
OpenSans-VariableFont_wdth,wght.ttf
OpenSans-Italic-VariableFont_wdth,wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Open Sans:
static/OpenSans_Condensed-Light.ttf
static/OpenSans_Condensed-Regular.ttf
static/OpenSans_Condensed-Medium.ttf
static/OpenSans_Condensed-SemiBold.ttf
static/OpenSans_Condensed-Bold.ttf
static/OpenSans_Condensed-ExtraBold.ttf
static/OpenSans_SemiCondensed-Light.ttf
static/OpenSans_SemiCondensed-Regular.ttf
static/OpenSans_SemiCondensed-Medium.ttf
static/OpenSans_SemiCondensed-SemiBold.ttf
static/OpenSans_SemiCondensed-Bold.ttf
static/OpenSans_SemiCondensed-ExtraBold.ttf
static/OpenSans-Light.ttf
static/OpenSans-Regular.ttf
static/OpenSans-Medium.ttf
static/OpenSans-SemiBold.ttf
static/OpenSans-Bold.ttf
static/OpenSans-ExtraBold.ttf
static/OpenSans_Condensed-LightItalic.ttf
static/OpenSans_Condensed-Italic.ttf
static/OpenSans_Condensed-MediumItalic.ttf
static/OpenSans_Condensed-SemiBoldItalic.ttf
static/OpenSans_Condensed-BoldItalic.ttf
static/OpenSans_Condensed-ExtraBoldItalic.ttf
static/OpenSans_SemiCondensed-LightItalic.ttf
static/OpenSans_SemiCondensed-Italic.ttf
static/OpenSans_SemiCondensed-MediumItalic.ttf
static/OpenSans_SemiCondensed-SemiBoldItalic.ttf
static/OpenSans_SemiCondensed-BoldItalic.ttf
static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf
static/OpenSans-LightItalic.ttf
static/OpenSans-Italic.ttf
static/OpenSans-MediumItalic.ttf
static/OpenSans-SemiBoldItalic.ttf
static/OpenSans-BoldItalic.ttf
static/OpenSans-ExtraBoldItalic.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.

View File

@@ -0,0 +1,31 @@
# 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 sdl3_resource_manager.cpp sdl3_resource_manager.hpp)
add_library(sdl3_client_res STATIC ${SRCS})
set_property(TARGET sdl3_client_res PROPERTY FOLDER "Client/SDL")
if(NOT WITH_SDL_LINK_SHARED)
target_link_libraries(sdl3_client_res ${SDL3_STATIC_LIBRARIES})
else()
target_link_libraries(sdl3_client_res ${SDL3_LIBRARIES})
endif()
target_link_libraries(sdl3_client_res sdl-common-client-res)
set_target_properties(sdl3_client_res PROPERTIES POSITION_INDEPENDENT_CODE ON INTERPROCEDURAL_OPTIMIZATION OFF)

View File

@@ -0,0 +1,43 @@
/**
* 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 "sdl3_resource_manager.hpp"
#include <iostream>
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
#endif
SDL_IOStream* SDL3ResourceManager::get(const std::string& type, const std::string& id)
{
if (useCompiledResources())
{
auto d = data(type, id);
if (!d)
return nullptr;
return SDL_IOFromConstMem(d->data(), d->size());
}
auto name = filename(type, id);
return SDL_IOFromFile(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 <SDL3/SDL.h>
#include <res/sdl_resource_manager.hpp>
class SDL3ResourceManager : public SDLResourceManager
{
public:
SDL3ResourceManager() = delete;
SDL3ResourceManager(const SDL3ResourceManager& other) = delete;
SDL3ResourceManager(const SDL3ResourceManager&& other) = delete;
~SDL3ResourceManager() = delete;
SDL3ResourceManager& operator=(const SDL3ResourceManager& other) = delete;
SDL3ResourceManager& operator=(SDL3ResourceManager&& other) = delete;
[[nodiscard]] static SDL_IOStream* get(const std::string& type, const std::string& id);
};

View File

@@ -0,0 +1,45 @@
#include "sdl_blend_mode_guard.hpp"
SdlBlendModeGuard::SdlBlendModeGuard(const std::shared_ptr<SDL_Renderer>& renderer,
SDL_BlendMode mode)
: _renderer(renderer)
{
const auto rcb = SDL_GetRenderDrawBlendMode(_renderer.get(), &_restore_mode);
if (!rcb)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[%s] SDL_GetRenderDrawBlendMode() failed with %s", __func__, SDL_GetError());
else
{
const auto rbm = SDL_SetRenderDrawBlendMode(_renderer.get(), mode);
if (!rbm)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[%s] SDL_SetRenderDrawBlendMode() failed with %s", __func__,
SDL_GetError());
else
_current_mode = mode;
}
}
bool SdlBlendModeGuard::update(SDL_BlendMode mode)
{
if (_current_mode != mode)
{
if (!SDL_SetRenderDrawBlendMode(_renderer.get(), mode))
{
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[%s] SDL_SetRenderDrawBlendMode() failed with %s", __func__,
SDL_GetError());
return false;
}
_current_mode = mode;
}
return true;
}
SdlBlendModeGuard::~SdlBlendModeGuard()
{
const auto rbm = SDL_SetRenderDrawBlendMode(_renderer.get(), _restore_mode);
if (!rbm)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[%s] SDL_SetRenderDrawBlendMode() failed with %s", __func__, SDL_GetError());
}

View File

@@ -0,0 +1,46 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 <memory>
#include <SDL3/SDL.h>
class SdlBlendModeGuard
{
public:
explicit SdlBlendModeGuard(const std::shared_ptr<SDL_Renderer>& renderer,
SDL_BlendMode mode = SDL_BLENDMODE_NONE);
~SdlBlendModeGuard();
SdlBlendModeGuard(SdlBlendModeGuard&& other) noexcept;
SdlBlendModeGuard(const SdlBlendModeGuard& other) = delete;
SdlBlendModeGuard& operator=(const SdlBlendModeGuard& other) = delete;
SdlBlendModeGuard& operator=(SdlBlendModeGuard&& other) = delete;
[[nodiscard]] bool update(SDL_BlendMode mode);
private:
SDL_BlendMode _restore_mode = SDL_BLENDMODE_INVALID;
SDL_BlendMode _current_mode = SDL_BLENDMODE_INVALID;
std::shared_ptr<SDL_Renderer> _renderer;
};

View File

@@ -0,0 +1,45 @@
/**
* 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"
SdlButton::SdlButton(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label, int id,
const SDL_FRect& rect)
: SdlSelectableWidget(renderer, rect), _id(id)
{
_backgroundcolor = { 0x69, 0x66, 0x63, 0xff };
_highlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
_mouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
_fontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
std::ignore = update_text(label);
std::ignore = update();
}
SdlButton::SdlButton(SdlButton&& other) noexcept = default;
SdlButton::~SdlButton() = default;
int SdlButton::id() const
{
return _id;
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <memory>
#include "sdl_selectable_widget.hpp"
class SdlButton : public SdlSelectableWidget
{
public:
SdlButton(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label, int id,
const SDL_FRect& rect);
SdlButton(SdlButton&& other) noexcept;
SdlButton(const SdlButton& other) = delete;
~SdlButton() override;
SdlButton& operator=(const SdlButton& other) = delete;
SdlButton& operator=(SdlButton&& other) = delete;
[[nodiscard]] int id() const;
private:
int _id;
};

View File

@@ -0,0 +1,108 @@
#include <cassert>
#include <algorithm>
#include "sdl_buttons.hpp"
static const Uint32 hpadding = 10;
SdlButtonList::~SdlButtonList() = default;
bool SdlButtonList::populate(std::shared_ptr<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() * (static_cast<size_t>(width) + hpadding) + hpadding;
size_t offsetX = static_cast<size_t>(total_width) -
std::min<size_t>(static_cast<size_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_FRect rect = { static_cast<float>(curOffsetX), static_cast<float>(offsetY),
static_cast<float>(width), static_cast<float>(height) };
auto button = std::make_shared<SdlButton>(renderer, labels.at(x), ids.at(x), rect);
_list.emplace_back(button);
}
return true;
}
std::shared_ptr<SdlButton> SdlButtonList::get_selected(const SDL_MouseButtonEvent& button)
{
const auto x = button.x;
const auto y = button.y;
return get_selected(x, y);
}
std::shared_ptr<SdlButton> SdlButtonList::get_selected(float x, float 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(float x, float y)
{
_mouseover = get_selected(x, y);
return _mouseover != nullptr;
}
void SdlButtonList::clear()
{
_list.clear();
_mouseover = nullptr;
_highlighted = nullptr;
_highlight_index = 0;
}
bool SdlButtonList::update()
{
for (auto& btn : _list)
{
if (!btn->highlight(btn == _highlighted))
return false;
if (!btn->mouseover(btn == _mouseover))
return false;
if (!btn->update())
return false;
}
return true;
}

View File

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

View File

@@ -0,0 +1,479 @@
/**
* 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_context.hpp"
#include "res/sdl3_resource_manager.hpp"
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)
{
std::ignore = hide();
}
SDLConnectionDialog::~SDLConnectionDialog()
{
resetTimer();
destroyWindow();
}
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(SdlConnectionDialogWrapper::MSG_NONE);
}
bool SDLConnectionDialog::showInfo(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
auto rc = show(SdlConnectionDialogWrapper::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(SdlConnectionDialogWrapper::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(SdlConnectionDialogWrapper::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(SdlConnectionDialogWrapper::MSG_DISCARD);
}
bool SDLConnectionDialog::running() const
{
std::scoped_lock lock(_mux);
return _running;
}
bool SDLConnectionDialog::updateMsg(SdlConnectionDialogWrapper::MsgType type)
{
switch (type)
{
case SdlConnectionDialogWrapper::MSG_INFO:
case SdlConnectionDialogWrapper::MSG_WARN:
case SdlConnectionDialogWrapper::MSG_ERROR:
_type_active = type;
if (!createWindow())
return false;
break;
case SdlConnectionDialogWrapper::MSG_DISCARD:
resetTimer();
destroyWindow();
break;
default:
if (_window)
{
SDL_SetWindowTitle(_window.get(), _title.c_str());
}
break;
}
return true;
}
bool SDLConnectionDialog::setModal()
{
if (_window)
{
auto sdl = get_context(_context);
auto parent = sdl->getFirstWindow();
if (!parent)
return true;
if (!SDL_SetWindowParent(_window.get(), parent->window()))
return false;
if (!SDL_SetWindowModal(_window.get(), true))
return false;
if (!SDL_RaiseWindow(_window.get()))
return false;
}
return true;
}
bool SDLConnectionDialog::updateInternal()
{
std::scoped_lock lock(_mux);
for (auto& btn : _list)
{
if (!btn.widget.update_text(_msg))
return false;
}
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.get());
}
switch (event.type)
{
case SDL_EVENT_USER_RETRY_DIALOG:
{
std::scoped_lock lock(_mux);
auto type = static_cast<SdlConnectionDialogWrapper::MsgType>(event.user.code);
return updateMsg(type);
}
case SDL_EVENT_QUIT:
resetTimer();
destroyWindow();
return false;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
if (!update())
return false;
switch (event.key.key)
{
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_ESCAPE:
case SDLK_KP_ENTER:
if (event.type == SDL_EVENT_KEY_UP)
{
freerdp_abort_connect_context(_context);
std::ignore = sdl_push_quit();
}
break;
case SDLK_TAB:
if (!_buttons.set_highlight_next())
return false;
break;
default:
break;
}
return windowID == ev.windowID;
}
return false;
case SDL_EVENT_MOUSE_MOTION:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseMotionEvent&>(event);
_buttons.set_mouseover(event.button.x, event.button.y);
if (!update())
return false;
return windowID == ev.windowID;
}
return false;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
if (!update())
return false;
auto button = _buttons.get_selected(event.button);
if (button)
{
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP)
{
freerdp_abort_connect_context(_context);
std::ignore = sdl_push_quit();
}
}
return windowID == ev.windowID;
}
return false;
case SDL_EVENT_MOUSE_WHEEL:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
if (!update())
return false;
return windowID == ev.windowID;
}
return false;
case SDL_EVENT_FINGER_UP:
case SDL_EVENT_FINGER_DOWN:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
if (!update())
return false;
return windowID == ev.windowID;
}
return false;
default:
if ((event.type >= SDL_EVENT_WINDOW_FIRST) && (event.type <= SDL_EVENT_WINDOW_LAST))
{
auto& ev = reinterpret_cast<const SDL_WindowEvent&>(event);
switch (ev.type)
{
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
if (windowID == ev.windowID)
{
freerdp_abort_connect_context(_context);
std::ignore = sdl_push_quit();
}
break;
default:
if (!update())
return false;
if (!setModal())
return false;
break;
}
return windowID == ev.windowID;
}
return false;
}
}
bool SDLConnectionDialog::visible() const
{
std::scoped_lock lock(_mux);
return SdlWidgetList::visible();
}
bool SDLConnectionDialog::createWindow()
{
destroyWindow();
const size_t widget_height = 50;
const size_t widget_width = 600;
const size_t total_height = 300;
if (!reset(_title, widget_width, total_height))
return false;
if (!setModal())
return false;
SDL_Color res_bgcolor;
switch (_type_active)
{
case SdlConnectionDialogWrapper::MSG_INFO:
res_bgcolor = infocolor;
break;
case SdlConnectionDialogWrapper::MSG_WARN:
res_bgcolor = warncolor;
break;
case SdlConnectionDialogWrapper::MSG_ERROR:
res_bgcolor = errorcolor;
break;
case SdlConnectionDialogWrapper::MSG_DISCARD:
default:
res_bgcolor = _backgroundcolor;
break;
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
std::string res_name;
switch (_type_active)
{
case SdlConnectionDialogWrapper::MSG_INFO:
res_name = "icon_info.svg";
break;
case SdlConnectionDialogWrapper::MSG_WARN:
res_name = "icon_warning.svg";
break;
case SdlConnectionDialogWrapper::MSG_ERROR:
res_name = "icon_error.svg";
break;
case SdlConnectionDialogWrapper::MSG_DISCARD:
default:
res_name = "";
break;
}
const auto height = (total_height - 3.0f * vpadding) / 2.0f;
SDL_FRect iconRect{ hpadding, vpadding, widget_width / 4.0f - 2.0f * hpadding, height };
widget_cfg_t icon{ textcolor,
res_bgcolor,
{ _renderer, iconRect,
SDL3ResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
_list.emplace_back(std::move(icon));
iconRect.y += height;
widget_cfg_t logo{ textcolor,
_backgroundcolor,
{ _renderer, iconRect,
SDL3ResourceManager::get(SDLResourceManager::typeImages(),
"FreeRDP_Icon.svg") } };
_list.emplace_back(std::move(logo));
SDL_FRect rect = { widget_width / 4.0f, vpadding, widget_width * 3.0f / 4.0f,
total_height - 3ul * vpadding - widget_height };
#else
SDL_FRect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
total_height - 2ul * vpadding };
#endif
widget_cfg_t w{ textcolor, _backgroundcolor, { _renderer, rect } };
if (!w.widget.set_wrap(true, widget_width))
return false;
_list.emplace_back(std::move(w));
rect.y += widget_height + vpadding;
const std::vector<int> buttonids = { 1 };
const std::vector<std::string> buttonlabels = { "cancel" };
if (!_buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
total_height - widget_height - vpadding,
static_cast<Sint32>(widget_width / 2),
static_cast<Sint32>(widget_height)))
return false;
if (!_buttons.set_highlight(0))
return false;
if (!SDL_ShowWindow(_window.get()))
return false;
if (!SDL_RaiseWindow(_window.get()))
return false;
return true;
}
void SDLConnectionDialog::destroyWindow()
{
_buttons.clear();
_list.clear();
_renderer = nullptr;
_window = nullptr;
}
bool SDLConnectionDialog::show(SdlConnectionDialogWrapper::MsgType type, const char* fmt,
va_list ap)
{
std::scoped_lock lock(_mux);
_msg = print(fmt, ap);
return show(type);
}
bool SDLConnectionDialog::show(SdlConnectionDialogWrapper::MsgType type)
{
if (SDL_IsMainThread())
return updateMsg(type);
else
return sdl_push_user_event(SDL_EVENT_USER_RETRY_DIALOG, type);
}
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(uint32_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(void* pvthis, [[maybe_unused]] SDL_TimerID timerID,
[[maybe_unused]] Uint32 intervalMS)
{
auto self = static_cast<SDLConnectionDialog*>(pvthis);
std::ignore = self->hide();
self->_running = false;
return 0;
}

View File

@@ -0,0 +1,95 @@
/**
* 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 <SDL3/SDL.h>
#include "sdl_buttons.hpp"
#include "sdl_connection_dialog_wrapper.hpp"
#include "sdl_widget.hpp"
#include "sdl_widget_list.hpp"
class SDLConnectionDialog : public SdlWidgetList
{
public:
explicit SDLConnectionDialog(rdpContext* context);
SDLConnectionDialog(const SDLConnectionDialog& other) = delete;
SDLConnectionDialog(const SDLConnectionDialog&& other) = delete;
~SDLConnectionDialog() override;
SDLConnectionDialog& operator=(const SDLConnectionDialog& other) = delete;
SDLConnectionDialog& operator=(SDLConnectionDialog&& other) = delete;
[[nodiscard]] bool setTitle(const char* fmt, ...);
[[nodiscard]] bool showInfo(const char* fmt, ...);
[[nodiscard]] bool showWarn(const char* fmt, ...);
[[nodiscard]] bool showError(const char* fmt, ...);
[[nodiscard]] bool show();
[[nodiscard]] bool hide();
[[nodiscard]] bool running() const;
[[nodiscard]] bool wait(bool ignoreRdpContextQuit = false);
[[nodiscard]] bool handle(const SDL_Event& event);
[[nodiscard]] bool visible() const override;
protected:
[[nodiscard]] bool updateInternal() override;
private:
[[nodiscard]] bool createWindow();
void destroyWindow();
[[nodiscard]] bool updateMsg(SdlConnectionDialogWrapper::MsgType type);
[[nodiscard]] bool setModal();
[[nodiscard]] bool show(SdlConnectionDialogWrapper::MsgType type, const char* fmt, va_list ap);
[[nodiscard]] bool show(SdlConnectionDialogWrapper::MsgType type);
[[nodiscard]] static std::string print(const char* fmt, va_list ap);
[[nodiscard]] bool setTimer(Uint32 timeoutMS = 15000);
void resetTimer();
[[nodiscard]] static Uint32 timeout(void* pvthis, SDL_TimerID timerID, Uint32 intervalMS);
struct widget_cfg_t
{
SDL_Color fgcolor = {};
SDL_Color bgcolor = {};
SdlWidget widget;
};
rdpContext* _context = nullptr;
mutable std::mutex _mux;
std::string _title;
std::string _msg;
SdlConnectionDialogWrapper::MsgType _type_active = SdlConnectionDialogWrapper::MSG_NONE;
SDL_TimerID _timer = 0;
bool _running = false;
std::vector<widget_cfg_t> _list;
};

View File

@@ -0,0 +1,13 @@
#include "sdl_connection_dialog_hider.hpp"
#include "../sdl_context.hpp"
SDLConnectionDialogHider::SDLConnectionDialogHider(SdlContext* sdl)
: _sdl(sdl), _visible(_sdl->getDialog().isVisible())
{
_sdl->getDialog().show(false);
}
SDLConnectionDialogHider::~SDLConnectionDialogHider()
{
_sdl->getDialog().show(_visible);
}

View File

@@ -0,0 +1,39 @@
/**
* 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 "../sdl_types.hpp"
class SDLConnectionDialogHider
{
public:
explicit SDLConnectionDialogHider(SdlContext* sdl);
SDLConnectionDialogHider(const SDLConnectionDialogHider& other) = delete;
SDLConnectionDialogHider(SDLConnectionDialogHider&& other) = delete;
SDLConnectionDialogHider& operator=(const SDLConnectionDialogHider& other) = delete;
SDLConnectionDialogHider& operator=(SDLConnectionDialogHider&& other) = delete;
~SDLConnectionDialogHider();
private:
SdlContext* _sdl = nullptr;
bool _visible = false;
};

View File

@@ -0,0 +1,287 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 <sstream>
#include <freerdp/freerdp.h>
#include <freerdp/settings.h>
#include <freerdp/log.h>
#include "../sdl_utils.hpp"
#include "sdl_connection_dialog.hpp"
#include "sdl_connection_dialog_wrapper.hpp"
SdlConnectionDialogWrapper::SdlConnectionDialogWrapper(wLog* log) : _log(log)
{
}
SdlConnectionDialogWrapper::~SdlConnectionDialogWrapper() = default;
void SdlConnectionDialogWrapper::create(rdpContext* context)
{
const auto enabled =
freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks);
_connection_dialog.reset();
if (!enabled)
_connection_dialog = std::make_unique<SDLConnectionDialog>(context);
}
void SdlConnectionDialogWrapper::destroy()
{
_connection_dialog.reset();
}
bool SdlConnectionDialogWrapper::isRunning() const
{
std::unique_lock lock(_mux);
if (!_connection_dialog)
return false;
return _connection_dialog->running();
}
bool SdlConnectionDialogWrapper::isVisible() const
{
std::unique_lock lock(_mux);
if (!_connection_dialog)
return false;
return _connection_dialog->visible();
}
bool SdlConnectionDialogWrapper::handleEvent(const SDL_Event& event)
{
std::unique_lock lock(_mux);
if (!_connection_dialog)
return false;
return _connection_dialog->handle(event);
}
WINPR_ATTR_FORMAT_ARG(1, 0)
static std::string format(WINPR_FORMAT_ARG const char* fmt, va_list ap)
{
va_list ap1 = {};
va_copy(ap1, ap);
const int size = vsnprintf(nullptr, 0, fmt, ap1);
va_end(ap1);
if (size < 0)
return "";
std::string msg;
msg.resize(static_cast<size_t>(size) + 1);
va_list ap2 = {};
va_copy(ap2, ap);
std::ignore = vsnprintf(msg.data(), msg.size(), fmt, ap2);
va_end(ap2);
return msg;
}
void SdlConnectionDialogWrapper::setTitle(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
setTitle(format(fmt, ap));
va_end(ap);
}
void SdlConnectionDialogWrapper::setTitle(const std::string& title)
{
push(EventArg{ title });
}
void SdlConnectionDialogWrapper::showInfo(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
showInfo(format(fmt, ap));
va_end(ap);
}
void SdlConnectionDialogWrapper::showInfo(const std::string& info)
{
show(MSG_INFO, info);
}
void SdlConnectionDialogWrapper::showWarn(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
showWarn(format(fmt, ap));
va_end(ap);
}
void SdlConnectionDialogWrapper::showWarn(const std::string& info)
{
show(MSG_WARN, info);
}
void SdlConnectionDialogWrapper::showError(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
showError(format(fmt, ap));
va_end(ap);
}
void SdlConnectionDialogWrapper::showError(const std::string& error)
{
show(MSG_ERROR, error);
}
void SdlConnectionDialogWrapper::show(SdlConnectionDialogWrapper::MsgType type,
const std::string& msg)
{
push({ type, msg, true });
}
void SdlConnectionDialogWrapper::show(bool visible)
{
push(EventArg{ visible });
}
void SdlConnectionDialogWrapper::handleShow()
{
std::unique_lock lock(_mux);
while (!_queue.empty())
{
auto arg = _queue.front();
_queue.pop();
if (arg.hasTitle() && _connection_dialog)
{
std::ignore = _connection_dialog->setTitle(arg.title().c_str());
}
if (arg.hasType() && arg.hasMessage())
{
switch (arg.type())
{
case SdlConnectionDialogWrapper::MSG_INFO:
if (_connection_dialog)
std::ignore = _connection_dialog->showInfo(arg.message().c_str());
else
WLog_Print(_log, WLOG_INFO, "%s", arg.message().c_str());
break;
case SdlConnectionDialogWrapper::MSG_WARN:
if (_connection_dialog)
std::ignore = _connection_dialog->showWarn(arg.message().c_str());
else
WLog_Print(_log, WLOG_WARN, "%s", arg.message().c_str());
break;
case SdlConnectionDialogWrapper::MSG_ERROR:
if (_connection_dialog)
std::ignore = _connection_dialog->showError(arg.message().c_str());
else
WLog_Print(_log, WLOG_ERROR, "%s", arg.message().c_str());
break;
default:
break;
}
}
if (arg.hasVisibility() && _connection_dialog)
{
if (arg.visible())
std::ignore = _connection_dialog->show();
else
std::ignore = _connection_dialog->hide();
}
}
}
void SdlConnectionDialogWrapper::push(EventArg&& arg)
{
{
std::unique_lock lock(_mux);
_queue.push(std::move(arg));
}
auto rc = SDL_RunOnMainThread(
[](void* user)
{
auto dlg = static_cast<SdlConnectionDialogWrapper*>(user);
dlg->handleShow();
},
this, false);
if (!rc)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] SDL_RunOnMainThread failed with %s",
__func__, SDL_GetError());
}
SdlConnectionDialogWrapper::EventArg::EventArg(bool visible) : _visible(visible), _mask(8)
{
}
SdlConnectionDialogWrapper::EventArg::EventArg(const std::string& title) : _title(title), _mask(1)
{
}
SdlConnectionDialogWrapper::EventArg::EventArg(MsgType type, const std::string& msg, bool visible)
: _message(msg), _type(type), _visible(visible), _mask(14)
{
}
bool SdlConnectionDialogWrapper::EventArg::hasTitle() const
{
return _mask & 0x01;
}
const std::string& SdlConnectionDialogWrapper::EventArg::title() const
{
return _title;
}
bool SdlConnectionDialogWrapper::EventArg::hasMessage() const
{
return _mask & 0x02;
}
const std::string& SdlConnectionDialogWrapper::EventArg::message() const
{
return _message;
}
bool SdlConnectionDialogWrapper::EventArg::hasType() const
{
return _mask & 0x04;
}
SdlConnectionDialogWrapper::MsgType SdlConnectionDialogWrapper::EventArg::type() const
{
return _type;
}
bool SdlConnectionDialogWrapper::EventArg::hasVisibility() const
{
return _mask & 0x08;
}
bool SdlConnectionDialogWrapper::EventArg::visible() const
{
return _visible;
}
std::string SdlConnectionDialogWrapper::EventArg::str() const
{
std::stringstream ss;
ss << "{ title:" << _title << ", message:" << _message << ", type:" << _type
<< ", visible:" << _visible << ", mask:" << _mask << "}";
return ss.str();
}

View File

@@ -0,0 +1,122 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 <mutex>
#include <memory>
#include <string>
#include <queue>
#include <winpr/wlog.h>
#include <winpr/platform.h>
#include <freerdp/types.h>
#include <SDL3/SDL.h>
class SDLConnectionDialog;
class SdlConnectionDialogWrapper
{
public:
enum MsgType
{
MSG_NONE,
MSG_INFO,
MSG_WARN,
MSG_ERROR,
MSG_DISCARD
};
explicit SdlConnectionDialogWrapper(wLog* log);
~SdlConnectionDialogWrapper();
SdlConnectionDialogWrapper(const SdlConnectionDialogWrapper& other) = delete;
SdlConnectionDialogWrapper(SdlConnectionDialogWrapper&& other) = delete;
SdlConnectionDialogWrapper& operator=(const SdlConnectionDialogWrapper& other) = delete;
SdlConnectionDialogWrapper& operator=(SdlConnectionDialogWrapper&& other) = delete;
void create(rdpContext* context);
void destroy();
[[nodiscard]] bool isRunning() const;
[[nodiscard]] bool isVisible() const;
[[nodiscard]] bool handleEvent(const SDL_Event& event);
WINPR_ATTR_FORMAT_ARG(2, 3)
void setTitle(WINPR_FORMAT_ARG const char* fmt, ...);
void setTitle(const std::string& title);
WINPR_ATTR_FORMAT_ARG(2, 3)
void showInfo(WINPR_FORMAT_ARG const char* fmt, ...);
void showInfo(const std::string& info);
WINPR_ATTR_FORMAT_ARG(2, 3)
void showWarn(WINPR_FORMAT_ARG const char* fmt, ...);
void showWarn(const std::string& info);
WINPR_ATTR_FORMAT_ARG(2, 3)
void showError(WINPR_FORMAT_ARG const char* fmt, ...);
void showError(const std::string& error);
void show(SdlConnectionDialogWrapper::MsgType type, const std::string& msg);
void show(bool visible = true);
void handleShow();
private:
class EventArg
{
public:
explicit EventArg(bool visible);
explicit EventArg(const std::string& title);
EventArg(SdlConnectionDialogWrapper::MsgType type, const std::string& msg, bool visible);
[[nodiscard]] bool hasTitle() const;
[[nodiscard]] const std::string& title() const;
[[nodiscard]] bool hasMessage() const;
[[nodiscard]] const std::string& message() const;
[[nodiscard]] bool hasType() const;
[[nodiscard]] SdlConnectionDialogWrapper::MsgType type() const;
[[nodiscard]] bool hasVisibility() const;
[[nodiscard]] bool visible() const;
[[nodiscard]] std::string str() const;
private:
std::string _title;
std::string _message;
SdlConnectionDialogWrapper::MsgType _type = MSG_NONE;
bool _visible = false;
uint32_t _mask = 0;
};
void push(EventArg&& arg);
mutable std::mutex _mux;
std::unique_ptr<SDLConnectionDialog> _connection_dialog;
std::queue<EventArg> _queue;
wLog* _log = nullptr;
};

View File

@@ -0,0 +1,648 @@
/**
* 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 <SDL3/SDL.h>
#include "../sdl_context.hpp"
#include "sdl_dialogs.hpp"
#include "sdl_input_widget_pair.hpp"
#include "sdl_input_widget_pair_list.hpp"
#include "sdl_select.hpp"
#include "sdl_select_list.hpp"
#include "sdl_connection_dialog.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_EVENT_USER_SHOW_DIALOG, title, message, flags))
return 0;
if (!sdl_wait_for_result(context, SDL_EVENT_USER_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;
const char* target = freerdp_settings_get_server_name(instance->context->settings);
switch (reason)
{
case AUTH_RDSTLS:
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 guard(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_EVENT_USER_AUTH_DIALOG, title, u, d, p, reason))
return res;
if (!sdl_wait_for_result(instance->context, SDL_EVENT_USER_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);
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_EVENT_USER_SCARD_DIALOG, title, list.data(), count))
return res;
if (!sdl_wait_for_result(instance->context, SDL_EVENT_USER_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 BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
sdl->getDialog().setTitle("Retry connection to %s",
freerdp_settings_get_server_name(instance->context->settings));
if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
{
sdl->getDialog().showError("Unknown module %s, aborting", what);
return -1;
}
if (current == 0)
{
if (strcmp(what, "arm-transport") == 0)
sdl->getDialog().showWarn("[%s] Starting your VM. It may take up to 5 minutes", what);
}
if (!enabled)
{
sdl->getDialog().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->getDialog().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->getDialog().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);
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;
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)
{
if (!sdl_push_user_event(SDL_EVENT_USER_CERT_DIALOG, title, message))
return 0;
SDL_Event event = {};
if (!sdl_wait_for_result(context, SDL_EVENT_USER_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);
/* 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);
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_EVENT_USER_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_EVENT_USER_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_RDSTLS:
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 = { SdlInputWidgetPair::SDL_INPUT_READONLY,
SdlInputWidgetPair::SDL_INPUT_MASK };
if (args->result != AUTH_SMARTCARD_PIN)
{
if (args->result == AUTH_RDSTLS)
{
initial = { args->user ? args->user : "", args->password ? args->password : "" };
flags = { 0, SdlInputWidgetPair::SDL_INPUT_MASK };
}
else
{
initial = { args->user ? args->user : "", args->domain ? args->domain : "",
args->password ? args->password : "" };
flags = { 0, 0, SdlInputWidgetPair::SDL_INPUT_MASK };
}
}
ssize_t selected = -1;
switch (args->result)
{
case AUTH_SMARTCARD_PIN:
case AUTH_RDSTLS:
break;
default:
if (args->user)
{
selected++;
if (args->domain)
selected++;
}
break;
}
SdlInputWidgetPairList ilist(args->title, prompt, initial, flags, selected);
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_EVENT_USER_AUTH_RESULT, user, domain, pwd, rc);
}
BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
{
std::vector<std::string> vlist;
vlist.reserve(WINPR_ASSERTING_INT_CAST(size_t, count));
for (Sint32 x = 0; x < count; x++)
vlist.emplace_back(list[x]);
SdlSelectList slist(title, vlist);
Sint32 value = slist.run();
return sdl_push_user_event(SDL_EVENT_USER_SCARD_RESULT, value);
}
void sdl_dialogs_uninit()
{
TTF_Quit();
}
void sdl_dialogs_init()
{
TTF_Init();
}

View File

@@ -0,0 +1,59 @@
/**
* 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"
[[nodiscard]] BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password,
char** domain, rdp_auth_reason reason);
[[nodiscard]] BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list,
DWORD count, DWORD* choice, BOOL gateway);
[[nodiscard]] SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current,
void* userarg);
[[nodiscard]] 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);
[[nodiscard]] 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);
[[nodiscard]] int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
[[nodiscard]] BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type,
BOOL isDisplayMandatory, BOOL isConsentMandatory,
size_t length, const WCHAR* message);
[[nodiscard]] BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags);
[[nodiscard]] BOOL sdl_cert_dialog_show(const char* title, const char* message);
[[nodiscard]] BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list);
[[nodiscard]] BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args);
void sdl_dialogs_init();
void sdl_dialogs_uninit();

View File

@@ -0,0 +1,52 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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_input_widget.hpp"
SdlInputWidget::SdlInputWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect)
: SdlSelectableWidget(renderer, rect)
{
init();
}
SdlInputWidget::~SdlInputWidget() = default;
std::string SdlInputWidget::text() const
{
return _text;
}
void SdlInputWidget::init()
{
_backgroundcolor = { 0x56, 0x56, 0x56, 0xff };
_fontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
_highlightcolor = { 0x80, 0, 0, 0x60 };
_mouseovercolor = { 0, 0x80, 0, 0x60 };
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlInputWidget::SdlInputWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
SDL_IOStream* ops)
: SdlSelectableWidget(renderer, rect, ops)
{
init();
}
#endif
SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept = default;

View File

@@ -0,0 +1,43 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 "sdl_selectable_widget.hpp"
class SdlInputWidget : public SdlSelectableWidget
{
public:
SdlInputWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect);
SdlInputWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
SDL_IOStream* ops);
SdlInputWidget(SdlInputWidget&& other) noexcept;
SdlInputWidget(const SdlInputWidget& other) = delete;
SdlInputWidget& operator=(const SdlInputWidget& other) = delete;
SdlInputWidget& operator=(SdlInputWidget&& other) noexcept = delete;
~SdlInputWidget() override;
[[nodiscard]] std::string text() const;
private:
void init();
};

View File

@@ -0,0 +1,129 @@
/**
* 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_widget_pair.hpp"
#include <cassert>
#include <algorithm>
#include <string>
#include <utility>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "sdl_widget.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
SdlInputWidgetPair::SdlInputWidgetPair(std::shared_ptr<SDL_Renderer>& renderer,
const std::string& label, const std::string& initial,
Uint32 flags, size_t offset, size_t width, size_t height)
: _flags(flags), _label(renderer, { 0, static_cast<float>(offset * (height + _vpadding)),
static_cast<float>(width), static_cast<float>(height) }),
_input(renderer, { static_cast<float>(width + _hpadding),
static_cast<float>(offset * (height + _vpadding)),
static_cast<float>(width), static_cast<float>(height) })
{
std::ignore = _label.update_text(label);
std::ignore = update_input_text(initial);
}
SdlInputWidgetPair::SdlInputWidgetPair(SdlInputWidgetPair&& other) noexcept = default;
bool SdlInputWidgetPair::set_mouseover(bool mouseOver)
{
if (readonly())
return true;
return _input.mouseover(mouseOver);
}
bool SdlInputWidgetPair::set_highlight(bool highlight)
{
if (readonly())
return true;
return _input.highlight(highlight);
}
bool SdlInputWidgetPair::set_str(const std::string& text)
{
if (readonly())
return true;
return update_input_text(text);
}
bool SdlInputWidgetPair::remove_str(size_t count)
{
if (readonly())
return true;
auto text = _text;
if (text.empty())
return true;
auto newsize = text.size() - std::min<size_t>(text.size(), count);
return update_input_text(text.substr(0, newsize));
}
bool SdlInputWidgetPair::append_str(const std::string& text)
{
if (readonly())
return true;
auto itext = _text;
itext.append(text);
return update_input_text(itext);
}
const SDL_FRect& SdlInputWidgetPair::input_rect() const
{
return _input.rect();
}
std::string SdlInputWidgetPair::value() const
{
return _text;
}
bool SdlInputWidgetPair::readonly() const
{
return (_flags & SDL_INPUT_READONLY) != 0;
}
bool SdlInputWidgetPair::update()
{
// TODO: Draw the pair area
if (!_label.update())
return false;
if (!_input.update())
return false;
return true;
}
bool SdlInputWidgetPair::update_input_text(const std::string& txt)
{
_text = txt;
auto text = txt;
if (_flags & SDL_INPUT_MASK)
{
std::fill(text.begin(), text.end(), '*');
}
return _input.update_text(text);
}

View File

@@ -0,0 +1,73 @@
/**
* 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 <string>
#include <SDL3/SDL.h>
#include "sdl_widget.hpp"
#include "sdl_input_widget.hpp"
class SdlInputWidgetPair
{
public:
enum
{
SDL_INPUT_MASK = 1,
SDL_INPUT_READONLY = 2
};
SdlInputWidgetPair(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label,
const std::string& initial, Uint32 flags, size_t offset, size_t width,
size_t height);
SdlInputWidgetPair(SdlInputWidgetPair&& other) noexcept;
SdlInputWidgetPair(const SdlInputWidgetPair& other) = delete;
~SdlInputWidgetPair() = default;
SdlInputWidgetPair& operator=(const SdlInputWidgetPair& other) = delete;
SdlInputWidgetPair& operator=(SdlInputWidgetPair&& other) = delete;
bool set_mouseover(bool mouseOver);
bool set_highlight(bool highlight);
[[nodiscard]] bool set_str(const std::string& text);
[[nodiscard]] bool remove_str(size_t count);
[[nodiscard]] bool append_str(const std::string& text);
[[nodiscard]] const SDL_FRect& input_rect() const;
[[nodiscard]] std::string value() const;
[[nodiscard]] bool readonly() const;
[[nodiscard]] bool update();
protected:
[[nodiscard]] bool update_input_text(const std::string& txt);
Uint32 _vpadding = 5;
Uint32 _hpadding = 10;
private:
Uint32 _flags{};
SdlWidget _label;
SdlInputWidget _input;
std::string _text;
};

View File

@@ -0,0 +1,313 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 <algorithm>
#include <winpr/cast.h>
#include "sdl_widget_list.hpp"
#include "sdl_input_widget_pair_list.hpp"
static const Uint32 vpadding = 5;
SdlInputWidgetPairList::SdlInputWidgetPairList(const std::string& title,
const std::vector<std::string>& labels,
const std::vector<std::string>& initial,
const std::vector<Uint32>& flags, ssize_t selected)
{
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);
if (reset(title, total_width, total_height))
{
for (size_t x = 0; x < labels.size(); x++)
{
auto widget =
std::make_shared<SdlInputWidgetPair>(_renderer, labels.at(x), initial.at(x),
flags.at(x), x, widget_width, widget_heigth);
m_list.emplace_back(widget);
}
std::ignore = _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);
m_currentActiveTextInput = selected;
}
}
ssize_t SdlInputWidgetPairList::next(ssize_t current)
{
size_t iteration = 0;
auto val = static_cast<size_t>(current);
do
{
if (iteration >= m_list.size())
return -1;
if (iteration == 0)
{
if (current < 0)
val = 0;
else
val++;
}
else
val++;
iteration++;
val %= m_list.size();
} while (!valid(static_cast<ssize_t>(val)));
return static_cast<ssize_t>(val);
}
bool SdlInputWidgetPairList::valid(ssize_t current) const
{
if (current < 0)
return false;
auto s = static_cast<size_t>(current);
if (s >= m_list.size())
return false;
return !m_list.at(s)->readonly();
}
std::shared_ptr<SdlInputWidgetPair> SdlInputWidgetPairList::get(ssize_t index)
{
if (index < 0)
return nullptr;
auto s = static_cast<size_t>(index);
if (s >= m_list.size())
return nullptr;
return m_list.at(s);
}
SdlInputWidgetPairList::~SdlInputWidgetPairList()
{
m_list.clear();
_buttons.clear();
}
bool SdlInputWidgetPairList::updateInternal()
{
for (auto& btn : m_list)
{
if (!btn->update())
return false;
if (!btn->update())
return false;
}
return true;
}
ssize_t SdlInputWidgetPairList::get_index(const SDL_MouseButtonEvent& button)
{
const auto x = button.x;
const auto y = button.y;
for (size_t i = 0; i < m_list.size(); i++)
{
auto& cur = m_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 WINPR_ASSERTING_INT_CAST(ssize_t, i);
}
return -1;
}
int SdlInputWidgetPairList::run(std::vector<std::string>& result)
{
int res = -1;
ssize_t LastActiveTextInput = -1;
m_currentActiveTextInput = next(m_currentActiveTextInput);
if (!_window || !_renderer)
return -2;
if (!SDL_StartTextInput(_window.get()))
return -3;
try
{
bool running = true;
while (running)
{
if (!update())
throw;
SDL_Event event = {};
if (!SDL_WaitEvent(&event))
throw;
do
{
switch (event.type)
{
case SDL_EVENT_KEY_UP:
{
switch (event.key.key)
{
case SDLK_BACKSPACE:
{
auto cur = get(m_currentActiveTextInput);
if (cur)
{
if ((event.key.mod & SDL_KMOD_CTRL) != 0)
{
if (!cur->set_str(""))
throw;
}
else
{
if (!cur->remove_str(1))
throw;
}
}
}
break;
case SDLK_TAB:
m_currentActiveTextInput = next(m_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 ((event.key.mod & SDL_KMOD_CTRL) != 0)
{
auto cur = get(m_currentActiveTextInput);
if (cur)
{
auto text = SDL_GetClipboardText();
if (!cur->set_str(text))
throw;
}
}
break;
default:
break;
}
}
break;
case SDL_EVENT_TEXT_INPUT:
{
auto cur = get(m_currentActiveTextInput);
if (cur)
{
if (!cur->append_str(event.text.text))
throw;
}
}
break;
case SDL_EVENT_MOUSE_MOTION:
{
auto TextInputIndex = get_index(event.button);
for (auto& cur : m_list)
{
cur->set_mouseover(false);
}
if (TextInputIndex >= 0)
{
auto& cur = m_list.at(static_cast<size_t>(TextInputIndex));
cur->set_mouseover(true);
}
_buttons.set_mouseover(event.button.x, event.button.y);
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
{
auto val = get_index(event.button);
if (valid(val))
m_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_EVENT_QUIT:
res = INPUT_BUTTON_CANCEL;
running = false;
break;
default:
break;
}
} while (SDL_PollEvent(&event));
if (LastActiveTextInput != m_currentActiveTextInput)
{
LastActiveTextInput = m_currentActiveTextInput;
}
for (auto& cur : m_list)
{
if (!cur->set_highlight(false))
throw;
}
auto cur = get(m_currentActiveTextInput);
if (cur)
{
if (!cur->set_highlight(true))
throw;
}
auto rc = SDL_RenderPresent(_renderer.get());
if (!rc)
{
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] SDL_RenderPresent failed with %s",
__func__, SDL_GetError());
}
}
for (auto& cur : m_list)
result.push_back(cur->value());
}
catch (...)
{
res = -2;
}
if (!SDL_StopTextInput(_window.get()))
return -4;
return res;
}

View File

@@ -0,0 +1,63 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 <vector>
#include <SDL3/SDL.h>
#include "sdl_widget_list.hpp"
#include "sdl_input_widget_pair.hpp"
#include "sdl_buttons.hpp"
class SdlInputWidgetPairList : public SdlWidgetList
{
public:
SdlInputWidgetPairList(const std::string& title, const std::vector<std::string>& labels,
const std::vector<std::string>& initial,
const std::vector<Uint32>& flags, ssize_t selected = -1);
SdlInputWidgetPairList(const SdlInputWidgetPairList& other) = delete;
SdlInputWidgetPairList(SdlInputWidgetPairList&& other) = delete;
~SdlInputWidgetPairList() override;
SdlInputWidgetPairList& operator=(const SdlInputWidgetPairList& other) = delete;
SdlInputWidgetPairList& operator=(SdlInputWidgetPairList&& other) = delete;
[[nodiscard]] int run(std::vector<std::string>& result);
protected:
[[nodiscard]] bool updateInternal() override;
[[nodiscard]] ssize_t get_index(const SDL_MouseButtonEvent& button);
private:
enum
{
INPUT_BUTTON_ACCEPT = 1,
INPUT_BUTTON_CANCEL = -2
};
[[nodiscard]] ssize_t next(ssize_t current);
[[nodiscard]] bool valid(ssize_t current) const;
[[nodiscard]] std::shared_ptr<SdlInputWidgetPair> get(ssize_t index);
std::vector<std::shared_ptr<SdlInputWidgetPair>> m_list;
ssize_t m_currentActiveTextInput = -1;
};

View File

@@ -0,0 +1,45 @@
/**
* 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 <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "sdl_select.hpp"
#include "sdl_widget.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
#include "sdl_input_widget_pair_list.hpp"
SdlSelectWidget::SdlSelectWidget(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label,
const SDL_FRect& rect)
: SdlSelectableWidget(renderer, rect)
{
_backgroundcolor = { 0x69, 0x66, 0x63, 0xff };
_fontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
std::ignore = update_text(label);
}
SdlSelectWidget::~SdlSelectWidget() = default;
SdlSelectWidget::SdlSelectWidget(SdlSelectWidget&& other) noexcept = default;

View File

@@ -0,0 +1,39 @@
/**
* 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 <SDL3/SDL.h>
#include "sdl_selectable_widget.hpp"
class SdlSelectWidget : public SdlSelectableWidget
{
public:
SdlSelectWidget(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label,
const SDL_FRect& rect);
SdlSelectWidget(SdlSelectWidget&& other) noexcept;
SdlSelectWidget(const SdlSelectWidget& other) = delete;
~SdlSelectWidget() override;
SdlSelectWidget& operator=(const SdlSelectWidget& other) = delete;
SdlSelectWidget& operator=(SdlSelectWidget&& other) = delete;
};

View File

@@ -0,0 +1,200 @@
#include <cassert>
#include <winpr/cast.h>
#include "sdl_select_list.hpp"
#include "../sdl_utils.hpp"
static const Uint32 vpadding = 5;
SdlSelectList::SdlSelectList(const std::string& title, const std::vector<std::string>& labels)
{
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;
if (reset(title, widget_width, height))
{
SDL_FRect 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" };
std::ignore = _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() = default;
int SdlSelectList::run()
{
int res = -2;
ssize_t CurrentActiveTextInput = 0;
bool running = true;
if (!_window || !_renderer)
return -2;
try
{
while (running)
{
if (!update())
throw;
SDL_Event event = {};
if (!SDL_WaitEvent(&event))
throw;
do
{
switch (event.type)
{
case SDL_EVENT_KEY_DOWN:
switch (event.key.key)
{
case SDLK_UP:
case SDLK_BACKSPACE:
if (CurrentActiveTextInput > 0)
CurrentActiveTextInput--;
else if (_list.empty())
CurrentActiveTextInput = 0;
else
{
auto s = _list.size();
CurrentActiveTextInput =
WINPR_ASSERTING_INT_CAST(ssize_t, s) - 1;
}
break;
case SDLK_DOWN:
case SDLK_TAB:
if ((CurrentActiveTextInput < 0) || _list.empty())
CurrentActiveTextInput = 0;
else
{
auto s = _list.size();
CurrentActiveTextInput++;
if (s > 0)
{
CurrentActiveTextInput =
CurrentActiveTextInput %
WINPR_ASSERTING_INT_CAST(ssize_t, s);
}
}
break;
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_KP_ENTER:
running = false;
res = static_cast<int>(CurrentActiveTextInput);
break;
case SDLK_ESCAPE:
running = false;
res = INPUT_BUTTON_CANCEL;
break;
default:
break;
}
break;
case SDL_EVENT_MOUSE_MOTION:
{
auto TextInputIndex = get_index(event.button);
reset_mouseover();
if (TextInputIndex >= 0)
{
auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, TextInputIndex));
if (!cur.mouseover(true))
throw;
}
_buttons.set_mouseover(event.button.x, event.button.y);
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
{
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_EVENT_QUIT:
res = INPUT_BUTTON_CANCEL;
running = false;
break;
default:
break;
}
} while (SDL_PollEvent(&event));
reset_highlight();
if (CurrentActiveTextInput >= 0)
{
auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, CurrentActiveTextInput));
if (!cur.highlight(true))
throw;
}
if (!update())
throw;
}
}
catch (...)
{
return -1;
}
return res;
}
bool SdlSelectList::updateInternal()
{
for (auto& cur : _list)
{
if (!cur.update())
return false;
}
return true;
}
ssize_t SdlSelectList::get_index(const SDL_MouseButtonEvent& button)
{
const auto x = button.x;
const auto 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;
}
void SdlSelectList::reset_mouseover()
{
for (auto& cur : _list)
{
cur.mouseover(false);
}
}
void SdlSelectList::reset_highlight()
{
for (auto& cur : _list)
{
cur.highlight(false);
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <string>
#include <vector>
#include <SDL3/SDL.h>
#include "sdl_widget_list.hpp"
#include "sdl_select.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
class SdlSelectList : public SdlWidgetList
{
public:
SdlSelectList(const std::string& title, const std::vector<std::string>& labels);
SdlSelectList(const SdlSelectList& other) = delete;
SdlSelectList(SdlSelectList&& other) = delete;
~SdlSelectList() override;
SdlSelectList& operator=(const SdlSelectList& other) = delete;
SdlSelectList& operator=(SdlSelectList&& other) = delete;
[[nodiscard]] int run();
protected:
[[nodiscard]] bool updateInternal() override;
private:
enum
{
INPUT_BUTTON_ACCEPT = 0,
INPUT_BUTTON_CANCEL = -2
};
ssize_t get_index(const SDL_MouseButtonEvent& button);
void reset_mouseover();
void reset_highlight();
std::vector<SdlSelectWidget> _list;
};

View File

@@ -0,0 +1,68 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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_selectable_widget.hpp"
#include "sdl_blend_mode_guard.hpp"
SdlSelectableWidget::SdlSelectableWidget(std::shared_ptr<SDL_Renderer>& renderer,
const SDL_FRect& rect)
: SdlWidget(renderer, rect)
{
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlSelectableWidget::SdlSelectableWidget(std::shared_ptr<SDL_Renderer>& renderer,
const SDL_FRect& rect, SDL_IOStream* ops)
: SdlWidget(renderer, rect, ops)
{
}
#endif
SdlSelectableWidget::SdlSelectableWidget(SdlSelectableWidget&& other) noexcept = default;
SdlSelectableWidget::~SdlSelectableWidget() = default;
bool SdlSelectableWidget::highlight(bool enable)
{
_highlight = enable;
return update();
}
bool SdlSelectableWidget::mouseover(bool enable)
{
_mouseover = enable;
return update();
}
bool SdlSelectableWidget::updateInternal()
{
SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
std::vector<SDL_Color> colors;
colors.push_back(_backgroundcolor);
if (_highlight)
colors.push_back(_highlightcolor);
if (_mouseover)
colors.push_back(_mouseovercolor);
if (!fill(colors))
return false;
return SdlWidget::updateInternal();
}

View File

@@ -0,0 +1,50 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 "sdl_widget.hpp"
class SdlSelectableWidget : public SdlWidget
{
public:
SdlSelectableWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect);
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlSelectableWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
SDL_IOStream* ops);
#endif
SdlSelectableWidget(SdlSelectableWidget&& other) noexcept;
SdlSelectableWidget(const SdlSelectableWidget& other) = delete;
~SdlSelectableWidget() override;
SdlSelectableWidget& operator=(const SdlSelectableWidget& other) = delete;
SdlSelectableWidget& operator=(SdlSelectableWidget&& other) = delete;
bool highlight(bool enable);
bool mouseover(bool enable);
protected:
[[nodiscard]] bool updateInternal() override;
SDL_Color _highlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
SDL_Color _mouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
private:
bool _mouseover = false;
bool _highlight = false;
};

View File

@@ -0,0 +1,332 @@
/**
* 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 <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "sdl_widget.hpp"
#include "sdl_blend_mode_guard.hpp"
#include "../sdl_utils.hpp"
#include "res/sdl3_resource_manager.hpp"
#include <freerdp/log.h>
#if defined(WITH_SDL_IMAGE_DIALOGS)
#include <SDL3_image/SDL_image.h>
#endif
#define TAG CLIENT_TAG("SDL.widget")
static const Uint32 hpadding = 10;
SdlWidget::SdlWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect)
: _renderer(renderer),
_engine(TTF_CreateRendererTextEngine(renderer.get()), TTF_DestroyRendererTextEngine),
_rect(rect)
{
assert(renderer);
auto ops = SDL3ResourceManager::get(SDLResourceManager::typeFonts(),
"OpenSans-VariableFont_wdth,wght.ttf");
if (!ops)
widget_log_error(false, "SDLResourceManager::get");
else
{
_font = std::shared_ptr<TTF_Font>(TTF_OpenFontIO(ops, true, 64), TTF_CloseFont);
if (!_font)
widget_log_error(false, "TTF_OpenFontRW");
}
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlWidget::SdlWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
SDL_IOStream* ops)
: _renderer(renderer),
_engine(TTF_CreateRendererTextEngine(renderer.get()), TTF_DestroySurfaceTextEngine),
_rect(rect)
{
if (ops)
{
_image = std::shared_ptr<SDL_Texture>(IMG_LoadTexture_IO(renderer.get(), ops, true),
SDL_DestroyTexture);
if (!_image)
widget_log_error(false, "IMG_LoadTexture_IO");
}
}
#endif
SdlWidget::SdlWidget(SdlWidget&& other) noexcept
: _renderer(std::move(other._renderer)), _backgroundcolor(other._backgroundcolor),
_fontcolor(other._fontcolor), _text(std::move(other._text)), _font(std::move(other._font)),
_image(std::move(other._image)), _engine(std::move(other._engine)), _rect(other._rect),
_wrap(other._wrap), _text_width(other._text_width)
{
other._font = nullptr;
other._image = nullptr;
other._engine = nullptr;
}
std::shared_ptr<SDL_Texture> SdlWidget::render_text(const std::string& text, SDL_Color fgcolor,
SDL_FRect& src, SDL_FRect& dst) const
{
auto surface = std::shared_ptr<SDL_Surface>(
TTF_RenderText_Blended(_font.get(), text.c_str(), 0, fgcolor), SDL_DestroySurface);
if (!surface)
{
widget_log_error(false, "TTF_RenderText_Blended");
return nullptr;
}
auto texture = std::shared_ptr<SDL_Texture>(
SDL_CreateTextureFromSurface(_renderer.get(), surface.get()), SDL_DestroyTexture);
if (!texture)
{
widget_log_error(false, "SDL_CreateTextureFromSurface");
return nullptr;
}
if (!_engine)
{
widget_log_error(false, "TTF_CreateRendererTextEngine");
return nullptr;
}
std::unique_ptr<TTF_Text, decltype(&TTF_DestroyText)> txt(
TTF_CreateText(_engine.get(), _font.get(), text.c_str(), text.size()), TTF_DestroyText);
if (!txt)
{
widget_log_error(false, "TTF_CreateText");
return nullptr;
}
int w = 0;
int h = 0;
if (!TTF_GetTextSize(txt.get(), &w, &h))
{
widget_log_error(false, "TTF_GetTextSize");
return nullptr;
}
src.w = static_cast<float>(w);
src.h = static_cast<float>(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 float scale = dst.h / src.h;
const float sws = (src.w) * scale;
const float dws = (dst.w) / scale;
dst.w = std::min(dst.w, sws);
if (src.w > dws)
{
src.x = src.w - dws;
src.w = dws;
}
return texture;
}
static float scale(float dw, float dh)
{
const auto scale = dh / dw;
const auto dr = dh * scale;
return dr;
}
std::shared_ptr<SDL_Texture> SdlWidget::render_text_wrapped(const std::string& text,
SDL_Color fgcolor, SDL_FRect& src,
SDL_FRect& dst) const
{
assert(_text_width < INT32_MAX);
auto surface = std::shared_ptr<SDL_Surface>(
TTF_RenderText_Blended_Wrapped(_font.get(), text.c_str(), 0, fgcolor,
static_cast<int>(_text_width)),
SDL_DestroySurface);
if (!surface)
{
widget_log_error(false, "TTF_RenderText_Blended");
return nullptr;
}
src.w = static_cast<float>(surface->w);
src.h = static_cast<float>(surface->h);
auto texture = std::shared_ptr<SDL_Texture>(
SDL_CreateTextureFromSurface(_renderer.get(), surface.get()), SDL_DestroyTexture);
if (!texture)
{
widget_log_error(false, "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<float>(dh, dst.h);
return texture;
}
SdlWidget::~SdlWidget() = default;
bool SdlWidget::error_ex(bool success, const char* what, const char* file, size_t line,
const char* fkt)
{
if (success)
{
// Flip SDL3 convention to existing code convention to minimize code changes
return false;
}
static wLog* log = nullptr;
if (!log)
log = WLog_Get(TAG);
// Use -1 as it indicates error similar to SDL2 conventions
// sdl_log_error_ex treats any value other than 0 as SDL error
return sdl_log_error_ex(-1, log, what, file, line, fkt);
}
bool SdlWidget::updateInternal()
{
return update_text(_text);
}
bool SdlWidget::draw_rect(const SDL_FRect& rect, SDL_Color color) const
{
const auto drc = SDL_SetRenderDrawColor(_renderer.get(), color.r, color.g, color.b, color.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const auto rc = SDL_RenderFillRect(_renderer.get(), &rect);
return !widget_log_error(rc, "SDL_RenderFillRect");
}
bool SdlWidget::fill(SDL_Color color) const
{
std::vector<SDL_Color> colors = { color };
return fill(colors);
}
bool SdlWidget::fill(const std::vector<SDL_Color>& colors) const
{
SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
for (auto color : colors)
{
if (!draw_rect(_rect, color))
return false;
if (!guard.update(SDL_BLENDMODE_ADD))
return false;
}
return true;
}
bool SdlWidget::wrap() const
{
return _wrap;
}
bool SdlWidget::set_wrap(bool wrap, size_t width)
{
_wrap = wrap;
_text_width = width;
return _wrap;
}
const SDL_FRect& SdlWidget::rect() const
{
return _rect;
}
bool SdlWidget::clear() const
{
if (!_renderer)
return false;
SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
const auto drc = SDL_SetRenderDrawColor(_renderer.get(), _backgroundcolor.r, _backgroundcolor.g,
_backgroundcolor.b, _backgroundcolor.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const auto rcls = SDL_RenderRect(_renderer.get(), &_rect);
return !widget_log_error(rcls, "SDL_RenderRect");
}
bool SdlWidget::update()
{
if (!clear())
return false;
// TODO: Draw widget specifics
return updateInternal();
}
bool SdlWidget::update_text(const std::string& text)
{
_text = text;
if (_text.empty())
return true;
SDL_FRect src{};
SDL_FRect dst{};
std::shared_ptr<SDL_Texture> texture;
if (_image)
{
texture = _image;
dst = _rect;
auto propId = SDL_GetTextureProperties(_image.get());
auto w = SDL_GetNumberProperty(propId, SDL_PROP_TEXTURE_WIDTH_NUMBER, -1);
auto h = SDL_GetNumberProperty(propId, SDL_PROP_TEXTURE_HEIGHT_NUMBER, -1);
if (w < 0 || h < 0)
{
if (!widget_log_error(false, "SDL_GetTextureProperties"))
return false;
}
src.w = static_cast<float>(w);
src.h = static_cast<float>(h);
}
else if (_wrap)
texture = render_text_wrapped(_text, _fontcolor, src, dst);
else
texture = render_text(_text, _fontcolor, src, dst);
if (!texture)
return false;
const auto rc = SDL_RenderTexture(_renderer.get(), texture.get(), &src, &dst);
return !widget_log_error(rc, "SDL_RenderCopy");
}

View File

@@ -0,0 +1,100 @@
/**
* 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 <memory>
#include <vector>
#include <SDL3/SDL.h>
#include <SDL3_ttf/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(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect);
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect, SDL_IOStream* ops);
#endif
SdlWidget(const SdlWidget& other) = delete;
SdlWidget(SdlWidget&& other) noexcept;
virtual ~SdlWidget();
SdlWidget& operator=(const SdlWidget& other) = delete;
SdlWidget& operator=(SdlWidget&& other) = delete;
[[nodiscard]] bool fill(SDL_Color color) const;
[[nodiscard]] bool fill(const std::vector<SDL_Color>& colors) const;
[[nodiscard]] bool update_text(const std::string& text);
[[nodiscard]] bool wrap() const;
[[nodiscard]] bool set_wrap(bool wrap = true, size_t width = 0);
[[nodiscard]] const SDL_FRect& rect() const;
[[nodiscard]] bool update();
#define widget_log_error(res, what) SdlWidget::error_ex(res, what, __FILE__, __LINE__, __func__)
static bool error_ex(bool success, const char* what, const char* file, size_t line,
const char* fkt);
protected:
std::shared_ptr<SDL_Renderer> _renderer;
SDL_Color _backgroundcolor = { 0x56, 0x56, 0x56, 0xff };
SDL_Color _fontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
mutable std::string _text;
virtual bool clear() const;
virtual bool updateInternal();
private:
[[nodiscard]] bool draw_rect(const SDL_FRect& rect, SDL_Color color) const;
[[nodiscard]] std::shared_ptr<SDL_Texture>
render_text(const std::string& text, SDL_Color fgcolor, SDL_FRect& src, SDL_FRect& dst) const;
[[nodiscard]] std::shared_ptr<SDL_Texture> render_text_wrapped(const std::string& text,
SDL_Color fgcolor,
SDL_FRect& src,
SDL_FRect& dst) const;
std::shared_ptr<TTF_Font> _font = nullptr;
std::shared_ptr<SDL_Texture> _image = nullptr;
std::shared_ptr<TTF_TextEngine> _engine = nullptr;
SDL_FRect _rect = {};
bool _wrap = false;
size_t _text_width = 0;
};

View File

@@ -0,0 +1,63 @@
#include "sdl_widget_list.hpp"
#include "sdl_blend_mode_guard.hpp"
SdlWidgetList::~SdlWidgetList() = default;
bool SdlWidgetList::reset(const std::string& title, size_t width, size_t height)
{
auto w = WINPR_ASSERTING_INT_CAST(int, width);
auto h = WINPR_ASSERTING_INT_CAST(int, height);
SDL_Renderer* renderer = nullptr;
SDL_Window* window = nullptr;
auto rc = SDL_CreateWindowAndRenderer(
title.c_str(), w, h, SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS, &window, &renderer);
_renderer = std::shared_ptr<SDL_Renderer>(renderer, SDL_DestroyRenderer);
_window = std::shared_ptr<SDL_Window>(window, SDL_DestroyWindow);
if (!rc)
widget_log_error(rc, "SDL_CreateWindowAndRenderer");
return rc;
}
bool SdlWidgetList::visible() const
{
if (!_window || !_renderer)
return false;
auto flags = SDL_GetWindowFlags(_window.get());
return (flags & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0;
}
bool SdlWidgetList::clearWindow()
{
if (!_renderer)
return false;
SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
const auto drc = SDL_SetRenderDrawColor(_renderer.get(), _backgroundcolor.r, _backgroundcolor.g,
_backgroundcolor.b, _backgroundcolor.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const auto rcls = SDL_RenderClear(_renderer.get());
return !widget_log_error(rcls, "SDL_RenderClear");
}
bool SdlWidgetList::update()
{
if (!visible())
return true;
if (!clearWindow())
return false;
if (!updateInternal())
return false;
if (!_buttons.update())
return false;
auto rc = SDL_RenderPresent(_renderer.get());
if (!rc)
{
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] SDL_RenderPresent failed with %s", __func__,
SDL_GetError());
}
return rc;
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <memory>
#include <SDL3/SDL.h>
#include <winpr/assert.h>
#include "sdl_buttons.hpp"
class SdlWidgetList
{
public:
SdlWidgetList() = default;
SdlWidgetList(const SdlWidgetList& other) = delete;
SdlWidgetList(SdlWidgetList&& other) = delete;
SdlWidgetList& operator=(const SdlWidgetList& other) = delete;
SdlWidgetList& operator=(SdlWidgetList&& other) = delete;
virtual ~SdlWidgetList();
[[nodiscard]] virtual bool reset(const std::string& title, size_t width, size_t height);
[[nodiscard]] virtual bool visible() const;
protected:
[[nodiscard]] bool update();
[[nodiscard]] virtual bool clearWindow();
[[nodiscard]] virtual bool updateInternal() = 0;
std::shared_ptr<SDL_Window> _window;
std::shared_ptr<SDL_Renderer> _renderer;
SdlButtonList _buttons;
SDL_Color _backgroundcolor{ 0x38, 0x36, 0x35, 0xff };
};

View File

@@ -0,0 +1,5 @@
set(NAMES "TestSDLDialogInput;TestSDLDialogSelectList")
foreach(NAME ${NAMES})
add_executable(${NAME} ${NAME}.cpp)
target_link_libraries(${NAME} PRIVATE sdl3-dialogs freerdp-client freerdp)
endforeach()

View File

@@ -0,0 +1,59 @@
#include "../sdl_dialogs.hpp"
#include "../sdl_input_widget_pair_list.hpp"
#include <freerdp/api.h>
#include <winpr/wlog.h>
typedef int (*fkt_t)(void);
BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line,
const char* fkt)
{
WINPR_UNUSED(file);
WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, "xxx");
return TRUE;
}
static int auth_dialogs(void)
{
const std::string title = "sometitle";
std::vector<std::string> result;
std::vector<std::string> initial{ "Smartcard", "abc", "def" };
std::vector<Uint32> flags = { SdlInputWidgetPair::SDL_INPUT_READONLY,
SdlInputWidgetPair::SDL_INPUT_MASK, 0 };
const std::vector<std::string>& labels{ "foo", "bar", "gaga" };
SdlInputWidgetPairList ilist(title.c_str(), labels, initial, flags);
auto rc = ilist.run(result);
if ((result.size() < labels.size()))
rc = -1;
return rc;
}
static int runTest(fkt_t fkt)
{
int rc;
SDL_Init(SDL_INIT_VIDEO);
sdl_dialogs_init();
try
{
rc = fkt();
}
catch (int e)
{
rc = e;
}
sdl_dialogs_uninit();
SDL_Quit();
return rc;
}
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
return runTest(auth_dialogs);
}

View File

@@ -0,0 +1,47 @@
#include "../sdl_dialogs.hpp"
#include "../sdl_select_list.hpp"
#include <freerdp/api.h>
#include <winpr/wlog.h>
typedef int (*fkt_t)(void);
BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line,
const char* fkt)
{
WINPR_UNUSED(file);
WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, "xxx");
return TRUE;
}
static int select_dialogs(void)
{
const std::vector<std::string> labels{ "foo", "bar", "gaga", "blabla" };
SdlSelectList list{ "title", labels };
return list.run();
}
static int runTest(fkt_t fkt)
{
int rc;
SDL_Init(SDL_INIT_VIDEO);
sdl_dialogs_init();
try
{
rc = fkt();
}
catch (int e)
{
rc = e;
}
sdl_dialogs_uninit();
SDL_Quit();
return rc;
}
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
return runTest(select_dialogs);
}

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/SDL3")
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,93 @@
/**
* 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_context.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);
if (!sdl->getClipboardChannelContext().init(clip))
WLog_Print(sdl->getWLog(), WLOG_WARN, "Failed to initialize clipboard channel");
}
else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
{
auto disp = reinterpret_cast<DispClientContext*>(e->pInterface);
WINPR_ASSERT(disp);
if (!sdl->getDisplayChannelContext().init(disp))
WLog_Print(sdl->getWLog(), WLOG_WARN, "Failed to initialize display channel");
}
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: 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);
if (!sdl->getClipboardChannelContext().uninit(clip))
WLog_Print(sdl->getWLog(), WLOG_WARN, "Failed to uninitialize clipboard channel");
clip->custom = nullptr;
}
else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
{
auto disp = reinterpret_cast<DispClientContext*>(e->pInterface);
WINPR_ASSERT(disp);
if (!sdl->getDisplayChannelContext().uninit(disp))
WLog_Print(sdl->getWLog(), WLOG_WARN, "Failed to uninitialize display channel");
disp->custom = nullptr;
}
else
freerdp_client_OnChannelDisconnectedEventHandler(context, e);
}

View File

@@ -0,0 +1,26 @@
/**
* 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>
void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client keyboard helper
*
* Copyright 2024 Armin Novak <armin.novak@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.
*/
#pragma once
#include <utility>
#include <vector>
#include <atomic>
#include <queue>
#include <map>
#include <winpr/wtypes.h>
#include <freerdp/freerdp.h>
#include <freerdp/client/client_cliprdr_file.h>
#include <SDL3/SDL.h>
#include "sdl_types.hpp"
#include "sdl_utils.hpp"
/** @brief a clipboard format request */
class ClipRequest
{
public:
ClipRequest(UINT32 format, const std::string& mime);
ClipRequest(const ClipRequest& other) = default;
ClipRequest(ClipRequest&& other) = default;
~ClipRequest() = default;
ClipRequest& operator=(const ClipRequest& other) = delete;
ClipRequest& operator=(ClipRequest&& other) = delete;
[[nodiscard]] uint32_t format() const;
[[nodiscard]] std::string formatstr() const;
[[nodiscard]] std::string mime() const;
[[nodiscard]] bool success() const;
void setSuccess(bool status);
private:
uint32_t _format;
std::string _mime;
bool _success;
};
class CliprdrFormat
{
public:
CliprdrFormat(uint32_t formatID, const char* formatName);
[[nodiscard]] uint32_t formatId() const;
[[nodiscard]] const char* formatName() const;
private:
uint32_t _formatID;
std::string _formatName;
};
/** @brief object that handles clipboard context for the SDL3 client */
class sdlClip
{
public:
explicit sdlClip(SdlContext* sdl);
virtual ~sdlClip();
sdlClip(const sdlClip&) = delete;
sdlClip(sdlClip&&) = delete;
sdlClip& operator=(const sdlClip&) = delete;
sdlClip& operator=(sdlClip&&) = delete;
[[nodiscard]] bool init(CliprdrClientContext* clip);
[[nodiscard]] bool uninit(CliprdrClientContext* clip);
[[nodiscard]] bool handleEvent(const SDL_ClipboardEvent& ev);
private:
[[nodiscard]] UINT SendClientCapabilities();
void clearServerFormats();
[[nodiscard]] UINT SendFormatListResponse(BOOL status);
[[nodiscard]] UINT SendDataResponse(const BYTE* data, size_t size);
[[nodiscard]] UINT SendDataRequest(uint32_t formatID, const std::string& mime);
[[nodiscard]] std::string getServerFormat(uint32_t id);
[[nodiscard]] uint32_t serverIdForMime(const std::string& mime);
[[nodiscard]] bool contains(const char** mime_types, Sint32 count);
[[nodiscard]] static UINT MonitorReady(CliprdrClientContext* context,
const CLIPRDR_MONITOR_READY* monitorReady);
[[nodiscard]] static UINT ReceiveServerCapabilities(CliprdrClientContext* context,
const CLIPRDR_CAPABILITIES* capabilities);
[[nodiscard]] static UINT ReceiveServerFormatList(CliprdrClientContext* context,
const CLIPRDR_FORMAT_LIST* formatList);
[[nodiscard]] static UINT
ReceiveFormatListResponse(CliprdrClientContext* context,
const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse);
[[nodiscard]] static std::shared_ptr<BYTE> ReceiveFormatDataRequestHandle(
sdlClip* clipboard, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest, uint32_t& len);
[[nodiscard]] static UINT
ReceiveFormatDataRequest(CliprdrClientContext* context,
const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest);
[[nodiscard]] static UINT
ReceiveFormatDataResponse(CliprdrClientContext* context,
const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse);
[[nodiscard]] static const void* SDLCALL ClipDataCb(void* userdata, const char* mime_type,
size_t* size);
static void SDLCALL ClipCleanCb(void* userdata);
[[nodiscard]] static bool mime_is_file(const std::string& mime);
[[nodiscard]] static bool mime_is_text(const std::string& mime);
[[nodiscard]] static bool mime_is_image(const std::string& mime);
[[nodiscard]] static bool mime_is_bmp(const std::string& mime);
[[nodiscard]] static bool mime_is_html(const std::string& mime);
SdlContext* _sdl = nullptr;
CliprdrFileContext* _file = nullptr;
CliprdrClientContext* _ctx = nullptr;
wLog* _log = nullptr;
wClipboard* _system = nullptr;
std::atomic<bool> _sync = false;
HANDLE _event = nullptr;
Uint64 _last_timestamp = 0;
std::vector<CliprdrFormat> _serverFormats;
CriticalSection _lock;
std::queue<ClipRequest> _request_queue;
struct cache_entry
{
cache_entry(size_t len, std::shared_ptr<void> p) : size(len), ptr(std::move(p))
{
}
size_t size;
std::shared_ptr<void> ptr;
};
std::map<std::string, cache_entry> _cache_data;
std::vector<const char*> _current_mimetypes;
std::string _uuid;
std::string _mime_uuid;
};

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";

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
/**
* 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 <map>
#include <memory>
#include <sstream>
#include <vector>
#include <mutex>
#include <queue>
#include <thread>
#include <atomic>
#include <freerdp/freerdp.h>
#include <SDL3/SDL.h>
#include <sdl_common_utils.hpp>
#include "sdl_window.hpp"
#include "sdl_disp.hpp"
#include "sdl_clip.hpp"
#include "sdl_input.hpp"
#include "dialogs/sdl_connection_dialog_wrapper.hpp"
class SdlContext
{
public:
enum CursorType
{
CURSOR_NULL,
CURSOR_DEFAULT,
CURSOR_IMAGE
};
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;
[[nodiscard]] bool redraw(bool suppress = false) const;
void setConnected(bool val);
[[nodiscard]] bool isConnected() const;
void cleanup();
[[nodiscard]] bool resizeable() const;
[[nodiscard]] bool toggleResizeable();
[[nodiscard]] bool setResizeable(bool enable);
[[nodiscard]] bool fullscreen() const;
[[nodiscard]] bool toggleFullscreen();
[[nodiscard]] bool setFullscreen(bool enter, bool forceOriginalDisplay = false);
[[nodiscard]] bool setMinimized();
[[nodiscard]] bool grabMouse() const;
[[nodiscard]] bool toggleGrabMouse();
[[nodiscard]] bool setGrabMouse(bool enter);
[[nodiscard]] bool grabKeyboard() const;
[[nodiscard]] bool toggleGrabKeyboard();
[[nodiscard]] bool setGrabKeyboard(bool enter);
[[nodiscard]] rdpContext* context() const;
[[nodiscard]] rdpClientContext* common() const;
[[nodiscard]] bool setCursor(CursorType type);
[[nodiscard]] bool setCursor(rdpPointer* cursor);
[[nodiscard]] rdpPointer* cursor() const;
[[nodiscard]] bool restoreCursor();
void setMonitorIds(const std::vector<SDL_DisplayID>& ids);
[[nodiscard]] const std::vector<SDL_DisplayID>& monitorIds() const;
[[nodiscard]] int64_t monitorId(uint32_t index) const;
void push(std::vector<SDL_Rect>&& rects);
[[nodiscard]] std::vector<SDL_Rect> pop();
void setHasCursor(bool val);
[[nodiscard]] bool hasCursor() const;
void setMetadata();
[[nodiscard]] int start();
[[nodiscard]] int join();
[[nodiscard]] bool shallAbort(bool ignoreDialogs = false);
[[nodiscard]] bool createWindows();
[[nodiscard]] bool updateWindowList();
[[nodiscard]] bool updateWindow(SDL_WindowID id);
[[nodiscard]] bool drawToWindows(const std::vector<SDL_Rect>& rects = {});
[[nodiscard]] bool drawToWindow(SdlWindow& window, const std::vector<SDL_Rect>& rects = {});
[[nodiscard]] bool minimizeAllWindows();
[[nodiscard]] int exitCode() const;
[[nodiscard]] SDL_PixelFormat pixelFormat() const;
[[nodiscard]] const SdlWindow* getWindowForId(SDL_WindowID id) const;
[[nodiscard]] SdlWindow* getWindowForId(SDL_WindowID id);
[[nodiscard]] SdlWindow* getFirstWindow();
[[nodiscard]] bool addDisplayWindow(SDL_DisplayID id);
[[nodiscard]] bool removeDisplayWindow(SDL_DisplayID id);
[[nodiscard]] bool detectDisplays();
[[nodiscard]] rdpMonitor getDisplay(SDL_DisplayID id) const;
[[nodiscard]] std::vector<SDL_DisplayID> getDisplayIds() const;
[[nodiscard]] sdlDispContext& getDisplayChannelContext();
[[nodiscard]] sdlInput& getInputChannelContext();
[[nodiscard]] sdlClip& getClipboardChannelContext();
[[nodiscard]] SdlConnectionDialogWrapper& getDialog();
[[nodiscard]] wLog* getWLog();
[[nodiscard]] bool moveMouseTo(const SDL_FPoint& pos);
[[nodiscard]] SDL_FPoint screenToPixel(SDL_WindowID id, const SDL_FPoint& pos);
[[nodiscard]] SDL_FPoint pixelToScreen(SDL_WindowID id, const SDL_FPoint& pos);
[[nodiscard]] SDL_FRect pixelToScreen(SDL_WindowID id, const SDL_FRect& pos);
[[nodiscard]] bool handleEvent(const SDL_Event& ev);
private:
[[nodiscard]] static BOOL preConnect(freerdp* instance);
[[nodiscard]] static BOOL postConnect(freerdp* instance);
static void postDisconnect(freerdp* instance);
static void postFinalDisconnect(freerdp* instance);
[[nodiscard]] static BOOL desktopResize(rdpContext* context);
[[nodiscard]] static BOOL playSound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound);
[[nodiscard]] static BOOL beginPaint(rdpContext* context);
[[nodiscard]] static BOOL endPaint(rdpContext* context);
[[nodiscard]] static DWORD WINAPI rdpThreadRun(SdlContext* sdl);
[[nodiscard]] bool eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev);
[[nodiscard]] SDL_FPoint applyLocalScaling(const SDL_FPoint& val) const;
void removeLocalScaling(float& x, float& y) const;
[[nodiscard]] bool handleEvent(const SDL_WindowEvent& ev);
[[nodiscard]] bool handleEvent(const SDL_DisplayEvent& ev);
[[nodiscard]] bool handleEvent(const SDL_MouseButtonEvent& ev);
[[nodiscard]] bool handleEvent(const SDL_MouseMotionEvent& ev);
[[nodiscard]] bool handleEvent(const SDL_MouseWheelEvent& ev);
[[nodiscard]] bool handleEvent(const SDL_TouchFingerEvent& ev);
void addOrUpdateDisplay(SDL_DisplayID id);
void deleteDisplay(SDL_DisplayID id);
[[nodiscard]] bool createPrimary();
[[nodiscard]] std::string windowTitle() const;
[[nodiscard]] bool waitForWindowsCreated();
void sdl_client_cleanup(int exit_code, const std::string& error_msg);
[[nodiscard]] int sdl_client_thread_connect(std::string& error_msg);
[[nodiscard]] int sdl_client_thread_run(std::string& error_msg);
[[nodiscard]] int error_info_to_error(DWORD* pcode, char** msg, size_t* len) const;
void applyMonitorOffset(SDL_WindowID window, float& x, float& y) const;
[[nodiscard]] std::vector<SDL_DisplayID>
updateDisplayOffsetsForNeighbours(SDL_DisplayID id,
const std::vector<SDL_DisplayID>& ignore = {});
void updateMonitorDataFromOffsets();
rdpContext* _context = nullptr;
wLog* _log = nullptr;
std::atomic<bool> _connected = false;
bool _cursor_visible = true;
rdpPointer* _cursor = nullptr;
CursorType _cursorType = CURSOR_NULL;
std::vector<SDL_DisplayID> _monitorIds;
std::mutex _queue_mux;
std::queue<std::vector<SDL_Rect>> _queue;
/* SDL */
bool _fullscreen = false;
bool _resizeable = false;
bool _grabMouse = false;
bool _grabKeyboard = false;
int _exitCode = -1;
std::atomic<bool> _rdpThreadRunning = false;
SDL_PixelFormat _sdlPixelFormat = SDL_PIXELFORMAT_UNKNOWN;
CriticalSection _critical;
using SDLSurfacePtr = std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)>;
SDLSurfacePtr _primary;
SDL_FPoint _localScale{ 1.0f, 1.0f };
sdlDispContext _disp;
sdlInput _input;
sdlClip _clip;
SdlConnectionDialogWrapper _dialog;
std::map<SDL_DisplayID, rdpMonitor> _displays;
std::map<SDL_WindowID, SdlWindow> _windows;
std::map<SDL_DisplayID, std::pair<SDL_Rect, SDL_Rect>> _offsets;
uint32_t _windowWidth = 0;
uint32_t _windowHeigth = 0;
WinPREvent _windowsCreatedEvent;
std::thread _thread;
};

View File

@@ -0,0 +1,488 @@
/**
* 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 <SDL3/SDL.h>
#include "sdl_disp.hpp"
#include "sdl_input.hpp"
#include "sdl_context.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;
static auto operator==(const DISPLAY_CONTROL_MONITOR_LAYOUT& a,
const DISPLAY_CONTROL_MONITOR_LAYOUT& b)
{
if (a.Flags != b.Flags)
return false;
if (a.Left != b.Left)
return false;
if (a.Top != b.Top)
return false;
if (a.Width != b.Width)
return false;
if (a.Height != b.Height)
return false;
if (a.PhysicalWidth != b.PhysicalWidth)
return false;
if (a.PhysicalHeight != b.PhysicalHeight)
return false;
if (a.Orientation != b.Orientation)
return false;
if (a.DesktopScaleFactor != b.DesktopScaleFactor)
return false;
if (a.DeviceScaleFactor != b.DeviceScaleFactor)
return false;
return true;
}
bool sdlDispContext::settings_changed(const std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT>& layout)
{
return (layout != _last_sent_layout);
}
bool sdlDispContext::sendResize()
{
auto settings = _sdl->context()->settings;
if (!settings)
return false;
if (!_activated || !_disp)
return true;
if (GetTickCount64() - _lastSentDate < RESIZE_MIN_DELAY)
return true;
_lastSentDate = GetTickCount64();
const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
auto monitors = static_cast<const rdpMonitor*>(
freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray));
return sendLayout(monitors, mcount);
}
bool sdlDispContext::setWindowResizeable()
{
return _sdl->setResizeable(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->getDisplayChannelContext();
*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))
{
if (!sdlDisp->setWindowResizeable())
return;
if (e->firstActivation)
return;
std::ignore = 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))
{
if (sdlDisp->setWindowResizeable())
std::ignore = sdlDisp->addTimer();
}
}
Uint32 sdlDispContext::OnTimer(void* param, [[maybe_unused]] SDL_TimerID timerID, Uint32 interval)
{
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->getWLog(), WLOG_TRACE, "checking for display changes...");
auto rc = sdlDisp->sendResize();
if (!rc)
WLog_Print(sdl->getWLog(), WLOG_TRACE, "sent new display layout, result %d", rc);
if (sdlDisp->_timer_retries++ >= MAX_RETRIES)
{
WLog_Print(sdl->getWLog(), WLOG_TRACE, "deactivate timer, retries exceeded");
return 0;
}
WLog_Print(sdl->getWLog(), WLOG_TRACE, "fire timer one more time");
return interval;
}
bool sdlDispContext::sendLayout(const rdpMonitor* monitors, size_t nmonitors)
{
WINPR_ASSERT(monitors);
WINPR_ASSERT(nmonitors > 0);
auto settings = _sdl->context()->settings;
WINPR_ASSERT(settings);
std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT> layouts;
layouts.reserve(nmonitors);
for (size_t i = 0; i < nmonitors; i++)
{
auto monitor = &monitors[i];
DISPLAY_CONTROL_MONITOR_LAYOUT layout = {};
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 ORIENTATION_PORTRAIT:
layout.Orientation = ORIENTATION_PORTRAIT;
break;
case ORIENTATION_LANDSCAPE_FLIPPED:
layout.Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
break;
case ORIENTATION_PORTRAIT_FLIPPED:
layout.Orientation = ORIENTATION_PORTRAIT_FLIPPED;
break;
case ORIENTATION_LANDSCAPE:
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 = monitor->attributes.desktopScaleFactor;
layout.DeviceScaleFactor = monitor->attributes.deviceScaleFactor;
auto mask = freerdp_settings_get_uint64(settings, FreeRDP_MonitorOverrideFlags);
if ((mask & FREERDP_MONITOR_OVERRIDE_ORIENTATION) != 0)
layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
if ((mask & FREERDP_MONITOR_OVERRIDE_DESKTOP_SCALE) != 0)
layout.DesktopScaleFactor =
freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
if ((mask & FREERDP_MONITOR_OVERRIDE_DEVICE_SCALE) != 0)
layout.DeviceScaleFactor =
freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
layouts.emplace_back(layout);
}
if (!settings_changed(layouts))
return true;
WINPR_ASSERT(_disp);
const size_t len = layouts.size();
WINPR_ASSERT(len <= UINT32_MAX);
const auto ret = IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp,
static_cast<UINT32>(len), layouts.data());
if (ret != CHANNEL_RC_OK)
return false;
_last_sent_layout = layouts;
return true;
}
bool sdlDispContext::addTimer()
{
if (SDL_WasInit(SDL_INIT_EVENTS) == 0)
return false;
SDL_RemoveTimer(_timer);
WLog_Print(_sdl->getWLog(), WLOG_TRACE, "adding new display check timer");
_timer_retries = 0;
if (!sendResize())
return false;
_timer = SDL_AddTimer(1000, sdlDispContext::OnTimer, this);
return true;
}
bool sdlDispContext::updateMonitor(SDL_WindowID id)
{
if (!freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_DynamicResolutionUpdate))
return true;
if (!_sdl->updateWindow(id))
return false;
if (!_sdl->updateWindowList())
return false;
return addTimer();
}
bool sdlDispContext::updateMonitors(SDL_EventType type, SDL_DisplayID displayID)
{
auto settings = _sdl->context()->settings;
if (!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
return true;
if (!freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
return true;
switch (type)
{
case SDL_EVENT_DISPLAY_ADDED:
if (!_sdl->addDisplayWindow(displayID))
return false;
break;
case SDL_EVENT_DISPLAY_REMOVED:
if (!_sdl->removeDisplayWindow(displayID))
return false;
break;
default:
break;
}
if (!_sdl->updateWindowList())
return false;
return addTimer();
}
bool sdlDispContext::handleEvent(const SDL_DisplayEvent& ev)
{
const auto cat = SDL_LOG_CATEGORY_APPLICATION;
switch (ev.type)
{
case SDL_EVENT_DISPLAY_ADDED:
SDL_LogDebug(cat, "A new display with id %u was connected", ev.displayID);
return updateMonitors(ev.type, ev.displayID);
case SDL_EVENT_DISPLAY_REMOVED:
SDL_LogDebug(cat, "The display with id %u was disconnected", ev.displayID);
return updateMonitors(ev.type, ev.displayID);
case SDL_EVENT_DISPLAY_ORIENTATION:
SDL_LogDebug(cat, "The orientation of display with id %u was changed", ev.displayID);
return updateMonitors(ev.type, ev.displayID);
case SDL_EVENT_DISPLAY_MOVED:
SDL_LogDebug(cat, "The display with id %u was moved", ev.displayID);
return updateMonitors(ev.type, ev.displayID);
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
SDL_LogDebug(cat, "The display with id %u changed scale", ev.displayID);
return updateMonitors(ev.type, ev.displayID);
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
SDL_LogDebug(cat, "The display with id %u changed mode", ev.displayID);
return updateMonitors(ev.type, ev.displayID);
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
SDL_LogDebug(cat, "The display with id %u changed desktop mode", ev.displayID);
return updateMonitors(ev.type, ev.displayID);
default:
return true;
}
}
bool sdlDispContext::handleEvent(const SDL_WindowEvent& ev)
{
auto window = _sdl->getWindowForId(ev.windowID);
if (!window)
return true;
auto bordered = freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_Decorations);
window->setBordered(bordered);
switch (ev.type)
{
case SDL_EVENT_WINDOW_HIDDEN:
case SDL_EVENT_WINDOW_MINIMIZED:
return _sdl->redraw(true);
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
return updateMonitor(ev.windowID);
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
return updateMonitor(ev.windowID);
case SDL_EVENT_WINDOW_EXPOSED:
case SDL_EVENT_WINDOW_SHOWN:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
if (!_sdl->redraw())
return false;
/* fallthrough */
WINPR_FALLTHROUGH
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_RESIZED:
return updateMonitor(ev.windowID);
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
WINPR_ASSERT(_sdl);
return _sdl->getInputChannelContext().keyboard_grab(ev.windowID, false);
case SDL_EVENT_WINDOW_MOUSE_ENTER:
WINPR_ASSERT(_sdl);
if (!_sdl->getInputChannelContext().keyboard_grab(ev.windowID, true))
return false;
return _sdl->getInputChannelContext().keyboard_focus_in();
case SDL_EVENT_WINDOW_FOCUS_GAINED:
return _sdl->getInputChannelContext().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 setWindowResizeable() ? 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;
}
return _sdl->setResizeable(true);
}
bool sdlDispContext::uninit(DispClientContext* disp)
{
if (!disp)
return false;
_disp = nullptr;
return _sdl->setResizeable(false);
}
sdlDispContext::sdlDispContext(SdlContext* sdl) : _sdl(sdl)
{
WINPR_ASSERT(_sdl);
WINPR_ASSERT(_sdl->context()->settings);
WINPR_ASSERT(_sdl->context()->pubSub);
auto pubSub = _sdl->context()->pubSub;
if (PubSub_SubscribeActivated(pubSub, sdlDispContext::OnActivated) < 0)
throw std::exception();
if (PubSub_SubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset) < 0)
throw std::exception();
std::ignore = addTimer();
}
sdlDispContext::~sdlDispContext()
{
wPubSub* pubSub = _sdl->context()->pubSub;
WINPR_ASSERT(pubSub);
PubSub_UnsubscribeActivated(pubSub, sdlDispContext::OnActivated);
PubSub_UnsubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
SDL_RemoveTimer(_timer);
}

Some files were not shown because too many files have changed in this diff Show More