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

View File

@@ -0,0 +1,78 @@
/**
* 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 <vector>
#include <freerdp/types.h>
#include <freerdp/event.h>
#include <freerdp/client/disp.h>
#include "sdl_types.hpp"
#include <SDL3/SDL.h>
class sdlDispContext
{
public:
explicit sdlDispContext(SdlContext* sdl);
sdlDispContext(const sdlDispContext& other) = delete;
sdlDispContext(sdlDispContext&& other) = delete;
virtual ~sdlDispContext();
sdlDispContext& operator=(const sdlDispContext& other) = delete;
sdlDispContext& operator=(sdlDispContext&& other) = delete;
[[nodiscard]] bool init(DispClientContext* disp);
[[nodiscard]] bool uninit(DispClientContext* disp);
[[nodiscard]] bool handleEvent(const SDL_DisplayEvent& ev);
[[nodiscard]] bool handleEvent(const SDL_WindowEvent& ev);
private:
[[nodiscard]] UINT DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
UINT32 maxMonitorAreaFactorB);
[[nodiscard]] bool setWindowResizeable();
[[nodiscard]] bool sendResize();
[[nodiscard]] bool settings_changed(const std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT>& layout);
[[nodiscard]] bool sendLayout(const rdpMonitor* monitors, size_t nmonitors);
[[nodiscard]] bool addTimer();
[[nodiscard]] bool updateMonitor(SDL_WindowID id);
[[nodiscard]] bool updateMonitors(SDL_EventType type, SDL_DisplayID displayID);
[[nodiscard]] 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);
[[nodiscard]] static Uint32 SDLCALL OnTimer(void* param, SDL_TimerID timerID, Uint32 interval);
SdlContext* _sdl = nullptr;
DispClientContext* _disp = nullptr;
UINT64 _lastSentDate = 0;
bool _activated = false;
bool _waitingResize = false;
SDL_TimerID _timer = 0;
unsigned _timer_retries = 0;
std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT> _last_sent_layout;
};

View File

@@ -0,0 +1,657 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP SDL UI
*
* 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 <iostream>
#include <memory>
#include <mutex>
#include <freerdp/config.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <freerdp/constants.h>
#include <freerdp/freerdp.h>
#include <freerdp/gdi/gdi.h>
#include <freerdp/streamdump.h>
#include <freerdp/utils/signal.h>
#include <freerdp/channels/channels.h>
#include <freerdp/client/channels.h>
#include <freerdp/client/cliprdr.h>
#include <freerdp/client/cmdline.h>
#include <freerdp/client/file.h>
#include <freerdp/log.h>
#include <winpr/assert.h>
#include <winpr/config.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <SDL3/SDL.h>
#if !defined(__MINGW32__)
#include <SDL3/SDL_main.h>
#endif
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_oldnames.h>
#include <SDL3/SDL_video.h>
#include <sdl_config.hpp>
#include "dialogs/sdl_connection_dialog_hider.hpp"
#include "dialogs/sdl_dialogs.hpp"
#include "scoped_guard.hpp"
#include "sdl_channels.hpp"
#include "sdl_freerdp.hpp"
#include "sdl_context.hpp"
#include "sdl_monitor.hpp"
#include "sdl_pointer.hpp"
#include "sdl_prefs.hpp"
#include "sdl_utils.hpp"
#if defined(_WIN32)
#define SDL_TAG CLIENT_TAG("SDL")
#endif
class ErrorMsg : public std::exception
{
public:
ErrorMsg(int rc, Uint32 type, const std::string& msg,
const std::string& sdlError = SDL_GetError());
[[nodiscard]] int rc() const;
[[nodiscard]] const char* what() const noexcept override;
private:
int _rc;
Uint32 _type;
std::string _msg;
std::string _sdlError;
std::string _buffer;
};
const char* ErrorMsg::what() const noexcept
{
return _buffer.c_str();
}
int ErrorMsg::rc() const
{
return _rc;
}
ErrorMsg::ErrorMsg(int rc, Uint32 type, const std::string& msg, const std::string& sdlError)
: _rc(rc), _type(type), _msg(msg), _sdlError(sdlError)
{
std::stringstream ss;
ss << _msg << " {" << sdl::utils::toString(_type) << "}{SDL:" << sdlError << "}";
_buffer = ss.str();
}
static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const char* signame,
[[maybe_unused]] void* context)
{
std::ignore = sdl_push_quit();
}
[[nodiscard]] static int sdl_run(SdlContext* sdl)
{
int rc = -1;
WINPR_ASSERT(sdl);
try
{
while (!sdl->shallAbort())
{
SDL_Event windowEvent = {};
while (!sdl->shallAbort() && SDL_WaitEventTimeout(nullptr, 1000))
{
/* Only poll standard SDL events and SDL_EVENT_USERS meant to create
* dialogs. do not process the dialog return value events here.
*/
const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_EVENT_FIRST,
SDL_EVENT_USER_RETRY_DIALOG);
if (prc < 0)
{
if (sdl_log_error(prc, sdl->getWLog(), "SDL_PeepEvents"))
continue;
}
#if defined(WITH_DEBUG_SDL_EVENTS)
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "got event %s [0x%08" PRIx32 "]",
sdl::utils::toString(windowEvent.type).c_str(), windowEvent.type);
#endif
if (sdl->shallAbort(true))
continue;
if (sdl->getDialog().handleEvent(windowEvent))
continue;
if (!sdl->handleEvent(windowEvent))
throw ErrorMsg{ -1, windowEvent.type, "sdl->handleEvent" };
switch (windowEvent.type)
{
case SDL_EVENT_QUIT:
std::ignore = freerdp_abort_connect_context(sdl->context());
break;
case SDL_EVENT_USER_CERT_DIALOG:
{
SDLConnectionDialogHider hider(sdl);
auto title = static_cast<const char*>(windowEvent.user.data1);
auto msg = static_cast<const char*>(windowEvent.user.data2);
if (!sdl_cert_dialog_show(title, msg))
throw ErrorMsg{ -1, windowEvent.type, "sdl_cert_dialog_show" };
}
break;
case SDL_EVENT_USER_SHOW_DIALOG:
{
SDLConnectionDialogHider hider(sdl);
auto title = static_cast<const char*>(windowEvent.user.data1);
auto msg = static_cast<const char*>(windowEvent.user.data2);
if (!sdl_message_dialog_show(title, msg, windowEvent.user.code))
throw ErrorMsg{ -1, windowEvent.type, "sdl_message_dialog_show" };
}
break;
case SDL_EVENT_USER_SCARD_DIALOG:
{
SDLConnectionDialogHider hider(sdl);
auto title = static_cast<const char*>(windowEvent.user.data1);
auto msg = static_cast<const char**>(windowEvent.user.data2);
if (!sdl_scard_dialog_show(title, windowEvent.user.code, msg))
throw ErrorMsg{ -1, windowEvent.type, "sdl_scard_dialog_show" };
}
break;
case SDL_EVENT_USER_AUTH_DIALOG:
{
SDLConnectionDialogHider hider(sdl);
if (!sdl_auth_dialog_show(
reinterpret_cast<const SDL_UserAuthArg*>(windowEvent.padding)))
throw ErrorMsg{ -1, windowEvent.type, "sdl_auth_dialog_show" };
}
break;
case SDL_EVENT_USER_UPDATE:
{
std::vector<SDL_Rect> rectangles;
do
{
rectangles = sdl->pop();
if (!sdl->drawToWindows(rectangles))
throw ErrorMsg{ -1, windowEvent.type, "sdl->drawToWindows" };
} while (!rectangles.empty());
}
break;
case SDL_EVENT_USER_CREATE_WINDOWS:
{
auto ctx = static_cast<SdlContext*>(windowEvent.user.data1);
if (!ctx->createWindows())
throw ErrorMsg{ -1, windowEvent.type, "sdl->createWindows" };
}
break;
case SDL_EVENT_USER_WINDOW_RESIZEABLE:
{
auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
const bool use = windowEvent.user.code != 0;
if (window)
window->resizeable(use);
}
break;
case SDL_EVENT_USER_WINDOW_FULLSCREEN:
{
auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
const bool enter = windowEvent.user.code != 0;
const bool forceOriginalDisplay = windowEvent.user.data2 != nullptr;
if (window)
window->fullscreen(enter, forceOriginalDisplay);
}
break;
case SDL_EVENT_USER_WINDOW_MINIMIZE:
if (!sdl->minimizeAllWindows())
throw ErrorMsg{ -1, windowEvent.type, "sdl->minimizeAllWindows" };
break;
case SDL_EVENT_USER_POINTER_NULL:
if (!sdl->setCursor(SdlContext::CURSOR_NULL))
throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
break;
case SDL_EVENT_USER_POINTER_DEFAULT:
if (!sdl->setCursor(SdlContext::CURSOR_DEFAULT))
throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
break;
case SDL_EVENT_USER_POINTER_POSITION:
{
const auto x =
static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data1));
const auto y =
static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data2));
if (!sdl->moveMouseTo(
{ static_cast<float>(x) * 1.0f, static_cast<float>(y) * 1.0f }))
throw ErrorMsg{ -1, windowEvent.type, "sdl->moveMouseTo" };
}
break;
case SDL_EVENT_USER_POINTER_SET:
if (!sdl->setCursor(static_cast<rdpPointer*>(windowEvent.user.data1)))
throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
break;
case SDL_EVENT_USER_QUIT:
default:
break;
}
}
}
rc = 1;
}
catch (ErrorMsg& msg)
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "[exception] %s", msg.what());
rc = msg.rc();
}
return rc;
}
/* Optional global initializer.
* Here we just register a signal handler to print out stack traces
* if available. */
[[nodiscard]] static BOOL sdl_client_global_init()
{
#if defined(_WIN32)
WSADATA wsaData = {};
const DWORD wVersionRequested = MAKEWORD(1, 1);
const int rc = WSAStartup(wVersionRequested, &wsaData);
if (rc != 0)
{
WLog_ERR(SDL_TAG, "WSAStartup failed with %s [%d]", gai_strerrorA(rc), rc);
return FALSE;
}
#endif
return (freerdp_handle_signals() == 0);
}
/* Optional global tear down */
static void sdl_client_global_uninit()
{
#if defined(_WIN32)
WSACleanup();
#endif
}
[[nodiscard]] static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
{
auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
if (!instance || !context)
return FALSE;
sdl->sdl = new SdlContext(context);
return sdl->sdl != nullptr;
}
static void sdl_client_free([[maybe_unused]] freerdp* instance, rdpContext* context)
{
auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
if (!context)
return;
delete sdl->sdl;
}
[[nodiscard]] static int sdl_client_start(rdpContext* context)
{
auto sdl = get_context(context);
WINPR_ASSERT(sdl);
return sdl->start();
}
[[nodiscard]] static int sdl_client_stop(rdpContext* context)
{
auto sdl = get_context(context);
WINPR_ASSERT(sdl);
return sdl->join();
}
static void RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
{
WINPR_ASSERT(pEntryPoints);
ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
pEntryPoints->GlobalInit = sdl_client_global_init;
pEntryPoints->GlobalUninit = sdl_client_global_uninit;
pEntryPoints->ContextSize = sizeof(sdl_rdp_context);
pEntryPoints->ClientNew = sdl_client_new;
pEntryPoints->ClientFree = sdl_client_free;
pEntryPoints->ClientStart = sdl_client_start;
pEntryPoints->ClientStop = sdl_client_stop;
}
static void context_free(sdl_rdp_context* sdl)
{
if (sdl)
freerdp_client_context_free(&sdl->common.context);
}
[[nodiscard]] static const char* category2str(int category)
{
switch (category)
{
case SDL_LOG_CATEGORY_APPLICATION:
return "SDL_LOG_CATEGORY_APPLICATION";
case SDL_LOG_CATEGORY_ERROR:
return "SDL_LOG_CATEGORY_ERROR";
case SDL_LOG_CATEGORY_ASSERT:
return "SDL_LOG_CATEGORY_ASSERT";
case SDL_LOG_CATEGORY_SYSTEM:
return "SDL_LOG_CATEGORY_SYSTEM";
case SDL_LOG_CATEGORY_AUDIO:
return "SDL_LOG_CATEGORY_AUDIO";
case SDL_LOG_CATEGORY_VIDEO:
return "SDL_LOG_CATEGORY_VIDEO";
case SDL_LOG_CATEGORY_RENDER:
return "SDL_LOG_CATEGORY_RENDER";
case SDL_LOG_CATEGORY_INPUT:
return "SDL_LOG_CATEGORY_INPUT";
case SDL_LOG_CATEGORY_TEST:
return "SDL_LOG_CATEGORY_TEST";
case SDL_LOG_CATEGORY_GPU:
return "SDL_LOG_CATEGORY_GPU";
case SDL_LOG_CATEGORY_RESERVED2:
return "SDL_LOG_CATEGORY_RESERVED2";
case SDL_LOG_CATEGORY_RESERVED3:
return "SDL_LOG_CATEGORY_RESERVED3";
case SDL_LOG_CATEGORY_RESERVED4:
return "SDL_LOG_CATEGORY_RESERVED4";
case SDL_LOG_CATEGORY_RESERVED5:
return "SDL_LOG_CATEGORY_RESERVED5";
case SDL_LOG_CATEGORY_RESERVED6:
return "SDL_LOG_CATEGORY_RESERVED6";
case SDL_LOG_CATEGORY_RESERVED7:
return "SDL_LOG_CATEGORY_RESERVED7";
case SDL_LOG_CATEGORY_RESERVED8:
return "SDL_LOG_CATEGORY_RESERVED8";
case SDL_LOG_CATEGORY_RESERVED9:
return "SDL_LOG_CATEGORY_RESERVED9";
case SDL_LOG_CATEGORY_RESERVED10:
return "SDL_LOG_CATEGORY_RESERVED10";
case SDL_LOG_CATEGORY_CUSTOM:
default:
return "SDL_LOG_CATEGORY_CUSTOM";
}
}
[[nodiscard]] static SDL_LogPriority wloglevel2dl(DWORD level)
{
switch (level)
{
case WLOG_TRACE:
return SDL_LOG_PRIORITY_VERBOSE;
case WLOG_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
case WLOG_INFO:
return SDL_LOG_PRIORITY_INFO;
case WLOG_WARN:
return SDL_LOG_PRIORITY_WARN;
case WLOG_ERROR:
return SDL_LOG_PRIORITY_ERROR;
case WLOG_FATAL:
return SDL_LOG_PRIORITY_CRITICAL;
case WLOG_OFF:
default:
return SDL_LOG_PRIORITY_VERBOSE;
}
}
[[nodiscard]] static DWORD sdlpriority2wlog(SDL_LogPriority priority)
{
DWORD level = WLOG_OFF;
switch (priority)
{
case SDL_LOG_PRIORITY_VERBOSE:
level = WLOG_TRACE;
break;
case SDL_LOG_PRIORITY_DEBUG:
level = WLOG_DEBUG;
break;
case SDL_LOG_PRIORITY_INFO:
level = WLOG_INFO;
break;
case SDL_LOG_PRIORITY_WARN:
level = WLOG_WARN;
break;
case SDL_LOG_PRIORITY_ERROR:
level = WLOG_ERROR;
break;
case SDL_LOG_PRIORITY_CRITICAL:
level = WLOG_FATAL;
break;
default:
break;
}
return level;
}
static void SDLCALL winpr_LogOutputFunction(void* userdata, int category, SDL_LogPriority priority,
const char* message)
{
auto sdl = static_cast<SdlContext*>(userdata);
WINPR_ASSERT(sdl);
const DWORD level = sdlpriority2wlog(priority);
auto log = sdl->getWLog();
if (!WLog_IsLevelActive(log, level))
return;
WLog_PrintTextMessage(log, level, __LINE__, __FILE__, __func__, "[%s] %s",
category2str(category), message);
}
static void sdl_quit()
{
const auto cat = SDL_LOG_CATEGORY_APPLICATION;
SDL_Event ev = {};
ev.type = SDL_EVENT_QUIT;
if (!SDL_PushEvent(&ev))
{
SDL_LogError(cat, "An error occurred: %s", SDL_GetError());
}
}
static void SDLCALL rdp_file_cb(void* userdata, const char* const* filelist,
WINPR_ATTR_UNUSED int filter)
{
auto rdp = static_cast<std::string*>(userdata);
const auto cat = SDL_LOG_CATEGORY_APPLICATION;
if (!filelist)
{
SDL_LogError(cat, "An error occurred: %s", SDL_GetError());
sdl_quit();
return;
}
else if (!*filelist)
{
SDL_LogError(cat, "The user did not select any file.");
SDL_LogError(cat, "Most likely, the dialog was canceled.");
sdl_quit();
return;
}
while (*filelist)
{
SDL_LogError(cat, "Full path to selected file: '%s'", *filelist);
*rdp = *filelist;
filelist++;
}
sdl_quit();
}
[[nodiscard]] static std::string getRdpFile()
{
const auto flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS;
SDL_DialogFileFilter filters[] = { { "RDP files", "rdp;rdpw" } };
std::string rdp;
bool running = true;
if (!SDL_Init(flags))
return {};
auto props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING,
"SDL Freerdp - Open a RDP file");
SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, filters);
SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, ARRAYSIZE(filters));
SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFILE, rdp_file_cb, &rdp, props);
SDL_DestroyProperties(props);
do
{
SDL_Event event = {};
std::ignore = SDL_PollEvent(&event);
switch (event.type)
{
case SDL_EVENT_QUIT:
running = false;
break;
default:
break;
}
} while (running);
SDL_Quit();
return rdp;
}
int main(int argc, char* argv[])
{
int rc = -1;
RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {};
/* Allocate the RDP context first, we need to pass it to SDL */
RdpClientEntry(&clientEntryPoints);
std::unique_ptr<sdl_rdp_context, void (*)(sdl_rdp_context*)> sdl_rdp(
reinterpret_cast<sdl_rdp_context*>(freerdp_client_context_new(&clientEntryPoints)),
context_free);
if (!sdl_rdp)
return -1;
auto sdl = sdl_rdp->sdl;
auto settings = sdl->context()->settings;
WINPR_ASSERT(settings);
std::string rdp_file;
std::vector<char*> args;
args.reserve(WINPR_ASSERTING_INT_CAST(size_t, argc));
for (auto x = 0; x < argc; x++)
args.push_back(argv[x]);
if (argc == 1)
{
rdp_file = getRdpFile();
if (!rdp_file.empty())
{
args.push_back(rdp_file.data());
}
}
auto status = freerdp_client_settings_parse_command_line(
settings, WINPR_ASSERTING_INT_CAST(int, args.size()), args.data(), FALSE);
sdl_rdp->sdl->setMetadata();
if (status)
{
rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
{
if (!sdl_list_monitors(sdl))
return -1;
}
else
{
switch (status)
{
case COMMAND_LINE_STATUS_PRINT:
case COMMAND_LINE_STATUS_PRINT_VERSION:
case COMMAND_LINE_STATUS_PRINT_BUILDCONFIG:
break;
case COMMAND_LINE_STATUS_PRINT_HELP:
default:
SdlPref::print_config_file_help(3);
break;
}
}
return rc;
}
/* Basic SDL initialization */
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return -1;
SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0");
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS, "0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
SDL_SetHint(SDL_HINT_PEN_TOUCH_EVENTS, "1");
SDL_SetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, "1");
/* Redirect SDL log messages to wLog */
SDL_SetLogOutputFunction(winpr_LogOutputFunction, sdl);
auto level = WLog_GetLogLevel(sdl->getWLog());
SDL_SetLogPriorities(wloglevel2dl(level));
auto backend = SDL_GetCurrentVideoDriver();
WLog_Print(sdl->getWLog(), WLOG_DEBUG, "client is using backend '%s'", backend);
sdl_dialogs_init();
/* SDL cleanup code if the client exits */
ScopeGuard guard(
[&]()
{
sdl->cleanup();
freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler);
sdl_dialogs_uninit();
SDL_Quit();
});
if (!sdl->detectDisplays())
return -1;
/* Initialize RDP */
auto context = sdl->context();
WINPR_ASSERT(context);
if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
return -1;
if (freerdp_client_start(context) != 0)
return -1;
rc = sdl_run(sdl);
if (freerdp_client_stop(context) != 0)
return -1;
if (sdl->exitCode() != 0)
rc = sdl->exitCode();
return rc;
}

View File

@@ -0,0 +1,22 @@
/**
* 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 "sdl_context.hpp"

View File

@@ -0,0 +1,717 @@
/**
* 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 "sdl_input.hpp"
#include "sdl_disp.hpp"
#include "sdl_context.hpp"
#include "sdl_utils.hpp"
#include "sdl_prefs.hpp"
#include "sdl_touch.hpp"
#include <SDL3/SDL_oldnames.h>
#include <map>
#include <freerdp/utils/string.h>
#include <freerdp/scancode.h>
#include <freerdp/locale/keyboard.h>
#include <freerdp/locale/locale.h>
#include <freerdp/log.h>
typedef struct
{
Uint32 sdl;
const char* sdl_name;
UINT32 rdp;
const char* rdp_name;
} scancode_entry_t;
#define STR(x) #x
#define ENTRY(x, y) { x, STR(x), y, #y }
static const scancode_entry_t map[] = {
ENTRY(SDL_SCANCODE_UNKNOWN, RDP_SCANCODE_UNKNOWN),
ENTRY(SDL_SCANCODE_A, RDP_SCANCODE_KEY_A),
ENTRY(SDL_SCANCODE_B, RDP_SCANCODE_KEY_B),
ENTRY(SDL_SCANCODE_C, RDP_SCANCODE_KEY_C),
ENTRY(SDL_SCANCODE_D, RDP_SCANCODE_KEY_D),
ENTRY(SDL_SCANCODE_E, RDP_SCANCODE_KEY_E),
ENTRY(SDL_SCANCODE_F, RDP_SCANCODE_KEY_F),
ENTRY(SDL_SCANCODE_G, RDP_SCANCODE_KEY_G),
ENTRY(SDL_SCANCODE_H, RDP_SCANCODE_KEY_H),
ENTRY(SDL_SCANCODE_I, RDP_SCANCODE_KEY_I),
ENTRY(SDL_SCANCODE_J, RDP_SCANCODE_KEY_J),
ENTRY(SDL_SCANCODE_K, RDP_SCANCODE_KEY_K),
ENTRY(SDL_SCANCODE_L, RDP_SCANCODE_KEY_L),
ENTRY(SDL_SCANCODE_M, RDP_SCANCODE_KEY_M),
ENTRY(SDL_SCANCODE_N, RDP_SCANCODE_KEY_N),
ENTRY(SDL_SCANCODE_O, RDP_SCANCODE_KEY_O),
ENTRY(SDL_SCANCODE_P, RDP_SCANCODE_KEY_P),
ENTRY(SDL_SCANCODE_Q, RDP_SCANCODE_KEY_Q),
ENTRY(SDL_SCANCODE_R, RDP_SCANCODE_KEY_R),
ENTRY(SDL_SCANCODE_S, RDP_SCANCODE_KEY_S),
ENTRY(SDL_SCANCODE_T, RDP_SCANCODE_KEY_T),
ENTRY(SDL_SCANCODE_U, RDP_SCANCODE_KEY_U),
ENTRY(SDL_SCANCODE_V, RDP_SCANCODE_KEY_V),
ENTRY(SDL_SCANCODE_W, RDP_SCANCODE_KEY_W),
ENTRY(SDL_SCANCODE_X, RDP_SCANCODE_KEY_X),
ENTRY(SDL_SCANCODE_Y, RDP_SCANCODE_KEY_Y),
ENTRY(SDL_SCANCODE_Z, RDP_SCANCODE_KEY_Z),
ENTRY(SDL_SCANCODE_1, RDP_SCANCODE_KEY_1),
ENTRY(SDL_SCANCODE_2, RDP_SCANCODE_KEY_2),
ENTRY(SDL_SCANCODE_3, RDP_SCANCODE_KEY_3),
ENTRY(SDL_SCANCODE_4, RDP_SCANCODE_KEY_4),
ENTRY(SDL_SCANCODE_5, RDP_SCANCODE_KEY_5),
ENTRY(SDL_SCANCODE_6, RDP_SCANCODE_KEY_6),
ENTRY(SDL_SCANCODE_7, RDP_SCANCODE_KEY_7),
ENTRY(SDL_SCANCODE_8, RDP_SCANCODE_KEY_8),
ENTRY(SDL_SCANCODE_9, RDP_SCANCODE_KEY_9),
ENTRY(SDL_SCANCODE_0, RDP_SCANCODE_KEY_0),
ENTRY(SDL_SCANCODE_RETURN, RDP_SCANCODE_RETURN),
ENTRY(SDL_SCANCODE_ESCAPE, RDP_SCANCODE_ESCAPE),
ENTRY(SDL_SCANCODE_BACKSPACE, RDP_SCANCODE_BACKSPACE),
ENTRY(SDL_SCANCODE_TAB, RDP_SCANCODE_TAB),
ENTRY(SDL_SCANCODE_SPACE, RDP_SCANCODE_SPACE),
ENTRY(SDL_SCANCODE_MINUS, RDP_SCANCODE_OEM_MINUS),
ENTRY(SDL_SCANCODE_CAPSLOCK, RDP_SCANCODE_CAPSLOCK),
ENTRY(SDL_SCANCODE_F1, RDP_SCANCODE_F1),
ENTRY(SDL_SCANCODE_F2, RDP_SCANCODE_F2),
ENTRY(SDL_SCANCODE_F3, RDP_SCANCODE_F3),
ENTRY(SDL_SCANCODE_F4, RDP_SCANCODE_F4),
ENTRY(SDL_SCANCODE_F5, RDP_SCANCODE_F5),
ENTRY(SDL_SCANCODE_F6, RDP_SCANCODE_F6),
ENTRY(SDL_SCANCODE_F7, RDP_SCANCODE_F7),
ENTRY(SDL_SCANCODE_F8, RDP_SCANCODE_F8),
ENTRY(SDL_SCANCODE_F9, RDP_SCANCODE_F9),
ENTRY(SDL_SCANCODE_F10, RDP_SCANCODE_F10),
ENTRY(SDL_SCANCODE_F11, RDP_SCANCODE_F11),
ENTRY(SDL_SCANCODE_F12, RDP_SCANCODE_F12),
ENTRY(SDL_SCANCODE_F13, RDP_SCANCODE_F13),
ENTRY(SDL_SCANCODE_F14, RDP_SCANCODE_F14),
ENTRY(SDL_SCANCODE_F15, RDP_SCANCODE_F15),
ENTRY(SDL_SCANCODE_F16, RDP_SCANCODE_F16),
ENTRY(SDL_SCANCODE_F17, RDP_SCANCODE_F17),
ENTRY(SDL_SCANCODE_F18, RDP_SCANCODE_F18),
ENTRY(SDL_SCANCODE_F19, RDP_SCANCODE_F19),
ENTRY(SDL_SCANCODE_F20, RDP_SCANCODE_F20),
ENTRY(SDL_SCANCODE_F21, RDP_SCANCODE_F21),
ENTRY(SDL_SCANCODE_F22, RDP_SCANCODE_F22),
ENTRY(SDL_SCANCODE_F23, RDP_SCANCODE_F23),
ENTRY(SDL_SCANCODE_F24, RDP_SCANCODE_F24),
ENTRY(SDL_SCANCODE_NUMLOCKCLEAR, RDP_SCANCODE_NUMLOCK),
ENTRY(SDL_SCANCODE_KP_DIVIDE, RDP_SCANCODE_DIVIDE),
ENTRY(SDL_SCANCODE_KP_MULTIPLY, RDP_SCANCODE_MULTIPLY),
ENTRY(SDL_SCANCODE_KP_MINUS, RDP_SCANCODE_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_DECIMAL),
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_MEDIA_NEXT_TRACK, RDP_SCANCODE_MEDIA_NEXT_TRACK),
ENTRY(SDL_SCANCODE_MEDIA_PREVIOUS_TRACK, RDP_SCANCODE_MEDIA_PREV_TRACK),
ENTRY(SDL_SCANCODE_MEDIA_STOP, RDP_SCANCODE_MEDIA_STOP),
ENTRY(SDL_SCANCODE_MEDIA_PLAY, RDP_SCANCODE_MEDIA_PLAY_PAUSE),
ENTRY(SDL_SCANCODE_MUTE, RDP_SCANCODE_VOLUME_MUTE),
ENTRY(SDL_SCANCODE_MEDIA_SELECT, RDP_SCANCODE_LAUNCH_MEDIA_SELECT),
ENTRY(SDL_SCANCODE_SYSREQ, RDP_SCANCODE_SYSREQ),
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),
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_DECIMAL),
ENTRY(SDL_SCANCODE_KP_HEXADECIMAL, RDP_SCANCODE_UNKNOWN),
ENTRY(SDL_SCANCODE_AC_REFRESH, RDP_SCANCODE_UNKNOWN),
ENTRY(SDL_SCANCODE_AC_BOOKMARKS, RDP_SCANCODE_UNKNOWN),
ENTRY(SDL_SCANCODE_MEDIA_EJECT, RDP_SCANCODE_UNKNOWN),
ENTRY(SDL_SCANCODE_MEDIA_REWIND, RDP_SCANCODE_UNKNOWN),
ENTRY(SDL_SCANCODE_MEDIA_FAST_FORWARD, RDP_SCANCODE_UNKNOWN)
};
[[nodiscard]] static UINT32 sdl_get_kbd_flags()
{
UINT32 flags = 0;
SDL_Keymod mod = SDL_GetModState();
if ((mod & SDL_KMOD_NUM) != 0)
flags |= KBD_SYNC_NUM_LOCK;
if ((mod & SDL_KMOD_CAPS) != 0)
flags |= KBD_SYNC_CAPS_LOCK;
if ((mod & SDL_KMOD_SCROLL) != 0)
flags |= KBD_SYNC_SCROLL_LOCK;
// TODO: KBD_SYNC_KANA_LOCK
return flags;
}
bool sdlInput::keyboard_sync_state()
{
const UINT32 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();
if (!freerdp_input_send_focus_in_event(input, WINPR_ASSERTING_INT_CAST(uint16_t, syncFlags)))
return false;
/* finish with a mouse pointer position like mstsc.exe if required */
// TODO: fullscreen/remote app
float fx = 0.0f;
float fy = 0.0f;
SDL_GetMouseState(&fx, &fy);
auto w = SDL_GetMouseFocus();
const auto& pos = _sdl->screenToPixel(SDL_GetWindowID(w), SDL_FPoint{ fx, fy });
return freerdp_client_send_button_event(_sdl->common(), FALSE, PTR_FLAGS_MOVE,
static_cast<Sint32>(pos.x), static_cast<Sint32>(pos.y));
}
/* This function is called to update the keyboard indicator LED */
BOOL sdlInput::keyboard_set_indicators(rdpContext* context, UINT16 led_flags)
{
WINPR_UNUSED(context);
SDL_Keymod state = SDL_KMOD_NONE;
if ((led_flags & KBD_SYNC_NUM_LOCK) != 0)
state |= SDL_KMOD_NUM;
if ((led_flags & KBD_SYNC_CAPS_LOCK) != 0)
state |= SDL_KMOD_CAPS;
if ((led_flags & KBD_SYNC_SCROLL_LOCK) != 0)
state |= SDL_KMOD_SCROLL;
if ((led_flags & KBD_SYNC_KANA_LOCK) != 0)
state |= SDL_KMOD_LEVEL5;
SDL_SetModState(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)
{
auto sdl = reinterpret_cast<SdlContext*>(context);
if (!context)
return FALSE;
WLog_Print(sdl->getWLog(), WLOG_WARN,
"KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32
", imeConvMode=%08" PRIx32 ") ignored",
imeId, imeState, imeConvMode);
return TRUE;
}
[[nodiscard]] static const std::map<std::string, uint32_t>& getSdlMap()
{
static std::map<std::string, uint32_t> s_map = {
{ "KMOD_LSHIFT", SDL_KMOD_LSHIFT }, { "KMOD_RSHIFT", SDL_KMOD_RSHIFT },
{ "KMOD_LCTRL", SDL_KMOD_LCTRL }, { "KMOD_RCTRL", SDL_KMOD_RCTRL },
{ "KMOD_LALT", SDL_KMOD_LALT }, { "KMOD_RALT", SDL_KMOD_RALT },
{ "KMOD_LGUI", SDL_KMOD_LGUI }, { "KMOD_RGUI", SDL_KMOD_RGUI },
{ "KMOD_NUM", SDL_KMOD_NUM }, { "KMOD_CAPS", SDL_KMOD_CAPS },
{ "KMOD_MODE", SDL_KMOD_MODE }, { "KMOD_SCROLL", SDL_KMOD_SCROLL },
{ "KMOD_CTRL", SDL_KMOD_CTRL }, { "KMOD_SHIFT", SDL_KMOD_SHIFT },
{ "KMOD_ALT", SDL_KMOD_ALT }, { "KMOD_GUI", SDL_KMOD_GUI },
{ "KMOD_NONE", SDL_KMOD_NONE }, { "SDL_KMOD_LSHIFT", SDL_KMOD_LSHIFT },
{ "SDL_KMOD_RSHIFT", SDL_KMOD_RSHIFT }, { "SDL_KMOD_LCTRL", SDL_KMOD_LCTRL },
{ "SDL_KMOD_RCTRL", SDL_KMOD_RCTRL }, { "SDL_KMOD_LALT", SDL_KMOD_LALT },
{ "SDL_KMOD_RALT", SDL_KMOD_RALT }, { "SDL_KMOD_LGUI", SDL_KMOD_LGUI },
{ "SDL_KMOD_RGUI", SDL_KMOD_RGUI }, { "SDL_KMOD_NUM", SDL_KMOD_NUM },
{ "SDL_KMOD_CAPS", SDL_KMOD_CAPS }, { "SDL_KMOD_MODE", SDL_KMOD_MODE },
{ "SDL_KMOD_SCROLL", SDL_KMOD_SCROLL }, { "SDL_KMOD_CTRL", SDL_KMOD_CTRL },
{ "SDL_KMOD_SHIFT", SDL_KMOD_SHIFT }, { "SDL_KMOD_ALT", SDL_KMOD_ALT },
{ "SDL_KMOD_GUI", SDL_KMOD_GUI }, { "SDL_KMOD_NONE", SDL_KMOD_NONE }
};
return s_map;
}
[[nodiscard]] static std::string modbyvalue(uint32_t val)
{
for (const auto& v : getSdlMap())
{
if (v.second == val)
{
return v.first;
}
}
return "KMOD_UNKNONW";
}
[[nodiscard]] static std::string masktostr(uint32_t mask)
{
if (mask == 0)
return "<NONE>";
std::string str = "<";
for (uint32_t x = 0; x < 32; x++)
{
uint32_t cmask = 1 << x;
if ((mask & cmask) != 0)
{
auto s = modbyvalue(cmask);
if (str.size() > 1)
{
str += ">+<";
}
str += s;
}
}
str += ">";
return str;
}
bool sdlInput::prefToEnabled()
{
bool enabled = 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 == SDL_KMOD_NONE)
enabled = false;
}
else
{
WLog_Print(_sdl->getWLog(), WLOG_WARN,
"Invalid config::SDL_KeyModMask entry value '%s', disabling hotkeys",
val.c_str());
enabled = false;
}
}
return enabled;
}
uint32_t sdlInput::prefToMask()
{
const auto& m = getSdlMap();
uint32_t mod = SDL_KMOD_NONE;
for (const auto& val : SdlPref::instance()->get_array("SDL_KeyModMask", { "KMOD_RSHIFT" }))
{
auto it = m.find(val);
if (it != m.end())
mod |= it->second;
}
return mod;
}
[[nodiscard]] static const char* sdl_scancode_name(Uint32 scancode)
{
for (const auto& cur : map)
{
if (cur.sdl == scancode)
return cur.sdl_name;
}
return "SDL_SCANCODE_UNKNOWN";
}
[[nodiscard]] 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;
}
#if defined(WITH_DEBUG_SDL_KBD_EVENTS)
[[nodiscard]] static const char* sdl_rdp_scancode_name(UINT32 scancode)
{
for (const auto& cur : map)
{
if (cur.rdp == scancode)
return cur.rdp_name;
}
return "RDP_SCANCODE_UNKNOWN";
}
[[nodiscard]] static UINT32 sdl_rdp_scancode_val(const char* scancodeName)
{
for (const auto& cur : map)
{
if (strcmp(cur.rdp_name, scancodeName) == 0)
return cur.rdp;
}
return RDP_SCANCODE_UNKNOWN;
}
#endif
UINT32 sdlInput::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_Print(_sdl->getWLog(), WLOG_DEBUG, "got %s [%s] -> [%s]", SDL_GetScancodeName(code),
sdl_scancode_name(scancode), sdl_rdp_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::handleEvent(const SDL_KeyboardEvent& ev)
{
const UINT32 rdp_scancode = scancode_to_rdp(ev.scancode);
const SDL_Keymod mods = SDL_GetModState();
if (_hotkeysEnabled && (mods & _hotkeyModmask) == _hotkeyModmask)
{
if (ev.type == SDL_EVENT_KEY_DOWN)
{
if (ev.scancode == _hotkeyFullscreen)
{
WLog_Print(_sdl->getWLog(), WLOG_INFO, "%s+<%s> pressed, toggling fullscreen state",
masktostr(_hotkeyModmask).c_str(), sdl_scancode_name(_hotkeyFullscreen));
if (!keyboard_sync_state())
return false;
return _sdl->toggleFullscreen();
}
if (ev.scancode == _hotkeyResizable)
{
WLog_Print(_sdl->getWLog(), WLOG_INFO, "%s+<%s> pressed, toggling resizeable state",
masktostr(_hotkeyModmask).c_str(), sdl_scancode_name(_hotkeyResizable));
if (!keyboard_sync_state())
return false;
return _sdl->toggleResizeable();
}
if (ev.scancode == _hotkeyGrab)
{
WLog_Print(_sdl->getWLog(), WLOG_INFO, "%s+<%s> pressed, toggling grab state",
masktostr(_hotkeyModmask).c_str(), sdl_scancode_name(_hotkeyGrab));
if (!keyboard_sync_state())
return false;
return keyboard_grab(ev.windowID, !_sdl->grabKeyboard());
}
if (ev.scancode == _hotkeyDisconnect)
{
WLog_Print(_sdl->getWLog(), WLOG_INFO, "%s+<%s> pressed, disconnecting RDP session",
masktostr(_hotkeyModmask).c_str(), sdl_scancode_name(_hotkeyDisconnect));
if (!keyboard_sync_state())
return false;
freerdp_abort_connect_context(_sdl->context());
return true;
}
if (ev.scancode == _hotkeyMinimize)
{
WLog_Print(_sdl->getWLog(), WLOG_INFO, "%s+<%s> pressed, minimizing client",
masktostr(_hotkeyModmask).c_str(), sdl_scancode_name(_hotkeyMinimize));
if (!keyboard_sync_state())
return false;
return _sdl->setMinimized();
}
}
}
#if defined(WITH_DEBUG_KBD)
{
const BOOL ex = RDP_SCANCODE_EXTENDED(rdp_scancode);
const DWORD sc = RDP_SCANCODE_CODE(rdp_scancode);
WLog_Print(_sdl->getWLog(), WLOG_DEBUG,
"SDL keycode: %02" PRIX32 " -> rdp code: [%04" PRIx16 "] %02" PRIX8 "%s",
ev.scancode, rdp_scancode, sc, ex ? " extended" : "");
}
#endif
auto scancode = freerdp_keyboard_remap_key(_remapTable, rdp_scancode);
#if defined(WITH_DEBUG_KBD)
{
const BOOL ex = RDP_SCANCODE_EXTENDED(scancode);
const DWORD sc = RDP_SCANCODE_CODE(scancode);
WLog_Print(_sdl->getWLog(), WLOG_DEBUG,
"SDL keycode: %02" PRIX32 " -> remapped rdp code: [%04" PRIx16 "] %02" PRIX8
"%s",
ev.scancode, scancode, sc, ex ? " extended" : "");
}
#endif
return freerdp_input_send_keyboard_event_ex(_sdl->context()->input,
ev.type == SDL_EVENT_KEY_DOWN, ev.repeat, scancode);
}
bool sdlInput::keyboard_grab(Uint32 windowID, bool enable)
{
const auto window = _sdl->getWindowForId(windowID);
if (!window)
return false;
auto settings = _sdl->context()->settings;
auto kbd_enabled = freerdp_settings_get_bool(settings, FreeRDP_GrabKeyboard);
auto status = enable && kbd_enabled;
if (!_sdl->setGrabKeyboard(status))
WLog_Print(_sdl->getWLog(), WLOG_WARN, "Failed to ungrab keyboard");
return window->grabKeyboard(status);
}
bool sdlInput::mouse_focus(Uint32 windowID)
{
if (_lastWindowID != windowID)
{
_lastWindowID = windowID;
auto window = _sdl->getWindowForId(windowID);
if (!window)
return false;
window->raise();
}
return true;
}
bool sdlInput::mouse_grab(Uint32 windowID, bool enable)
{
auto window = _sdl->getWindowForId(windowID);
if (!window)
return false;
if (!_sdl->setGrabMouse(enable))
WLog_Print(_sdl->getWLog(), WLOG_WARN, "Failed to ungrab mouse");
return window->grabMouse(enable);
}
sdlInput::sdlInput(SdlContext* sdl)
: _sdl(sdl), _lastWindowID(UINT32_MAX), _hotkeysEnabled(prefToEnabled()),
_hotkeyModmask(prefToMask())
{
_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);
}
bool sdlInput::initialize()
{
auto settings = _sdl->context()->settings;
WINPR_ASSERT(settings);
WINPR_ASSERT(!_remapTable);
{
auto list = freerdp_settings_get_string(settings, FreeRDP_KeyboardRemappingList);
_remapTable = freerdp_keyboard_remap_string_to_list(list);
if (!_remapTable)
return false;
}
if (freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout) == 0)
{
DWORD KeyboardLayout = 0;
freerdp_detect_keyboard_layout_from_system_locale(&KeyboardLayout);
if (KeyboardLayout == 0)
KeyboardLayout = ENGLISH_UNITED_STATES;
if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, KeyboardLayout))
return false;
}
return true;
}

View File

@@ -0,0 +1,84 @@
/**
* 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 <SDL3/SDL.h>
#include "sdl_types.hpp"
class sdlInput
{
public:
explicit sdlInput(SdlContext* sdl);
sdlInput(const sdlInput& other) = delete;
sdlInput(sdlInput&& other) = delete;
virtual ~sdlInput();
sdlInput& operator=(const sdlInput& other) = delete;
sdlInput& operator=(sdlInput&& other) = delete;
[[nodiscard]] bool initialize();
[[nodiscard]] bool keyboard_sync_state();
[[nodiscard]] bool keyboard_focus_in();
[[nodiscard]] bool handleEvent(const SDL_KeyboardEvent& ev);
[[nodiscard]] bool keyboard_grab(Uint32 windowID, bool enable);
[[nodiscard]] bool mouse_focus(Uint32 windowID);
[[nodiscard]] bool mouse_grab(Uint32 windowID, bool enable);
[[nodiscard]] static BOOL keyboard_set_indicators(rdpContext* context, UINT16 led_flags);
[[nodiscard]] static BOOL keyboard_set_ime_status(rdpContext* context, UINT16 imeId,
UINT32 imeState, UINT32 imeConvMode);
[[nodiscard]] bool prefToEnabled();
[[nodiscard]] uint32_t prefToMask();
[[nodiscard]] static uint32_t prefKeyValue(const std::string& key,
uint32_t fallback = SDL_SCANCODE_UNKNOWN);
private:
[[nodiscard]] static std::list<std::string> tokenize(const std::string& data,
const std::string& delimiter = ",");
[[nodiscard]] static bool extract(const std::string& token, uint32_t& key, uint32_t& value);
[[nodiscard]] UINT32 scancode_to_rdp(Uint32 scancode);
SdlContext* _sdl = nullptr;
Uint32 _lastWindowID = 0;
// hotkey handling
bool _hotkeysEnabled = false;
uint32_t _hotkeyModmask = 0; // modifier keys mask
uint32_t _hotkeyFullscreen = 0;
uint32_t _hotkeyResizable = 0;
uint32_t _hotkeyGrab = 0;
uint32_t _hotkeyDisconnect = 0;
uint32_t _hotkeyMinimize = 0;
FREERDP_REMAP_TABLE* _remapTable = nullptr;
};

View File

@@ -0,0 +1,311 @@
/**
* 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 <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <SDL3/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_context.hpp"
typedef struct
{
RECTANGLE_16 area;
RECTANGLE_16 workarea;
BOOL primary;
} MONITOR_INFO;
typedef struct
{
int nmonitors;
RECTANGLE_16 area;
RECTANGLE_16 workarea;
MONITOR_INFO* monitors;
} VIRTUAL_SCREEN;
/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071
*/
int sdl_list_monitors([[maybe_unused]] SdlContext* sdl)
{
SDL_Init(SDL_INIT_VIDEO);
int nmonitors = 0;
auto ids = SDL_GetDisplays(&nmonitors);
printf("listing %d monitors:\n", nmonitors);
for (int i = 0; i < nmonitors; i++)
{
SDL_Rect rect = {};
auto id = ids[i];
const auto brc = SDL_GetDisplayBounds(id, &rect);
const char* name = SDL_GetDisplayName(id);
if (!brc)
continue;
printf(" %s [%u] [%s] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", id, name, rect.w, rect.h,
rect.x, rect.y);
}
SDL_free(ids);
SDL_Quit();
return 0;
}
[[nodiscard]] static BOOL sdl_apply_mon_max_size(SdlContext* sdl, UINT32* pMaxWidth,
UINT32* pMaxHeight)
{
int32_t left = 0;
int32_t right = 0;
int32_t top = 0;
int32_t bottom = 0;
WINPR_ASSERT(pMaxWidth);
WINPR_ASSERT(pMaxHeight);
auto settings = sdl->context()->settings;
WINPR_ASSERT(settings);
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));
left = std::min(monitor->x, left);
top = std::min(monitor->y, top);
right = std::max(monitor->x + monitor->width, right);
bottom = std::max(monitor->y + monitor->height, bottom);
}
const int32_t w = right - left;
const int32_t h = bottom - top;
*pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, w);
*pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, h);
return TRUE;
}
[[nodiscard]] 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(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(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;
}
[[nodiscard]] static BOOL sdl_apply_display_properties(SdlContext* sdl)
{
WINPR_ASSERT(sdl);
rdpSettings* settings = sdl->context()->settings;
WINPR_ASSERT(settings);
std::vector<rdpMonitor> monitors;
if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
{
if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
{
if (sdl->monitorIds().empty())
return FALSE;
const auto id = sdl->monitorIds().front();
auto monitor = sdl->getDisplay(id);
monitor.is_primary = true;
monitor.x = 0;
monitor.y = 0;
monitors.emplace_back(monitor);
return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(),
monitors.size());
}
return TRUE;
}
for (const auto& id : sdl->monitorIds())
{
const auto monitor = sdl->getDisplay(id);
monitors.emplace_back(monitor);
}
return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(),
monitors.size());
}
[[nodiscard]] 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)
{
SDL_DisplayID id = 0;
const auto& ids = sdl->monitorIds();
if (!ids.empty())
{
id = ids.front();
}
sdl->setMonitorIds({ id });
}
if (!sdl_apply_display_properties(sdl))
return FALSE;
return sdl_apply_max_size(sdl, pMaxWidth, pMaxHeight);
}
return sdl_apply_mon_max_size(sdl, pMaxWidth, pMaxHeight);
}
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 auto& ids = sdl->getDisplayIds();
auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
if (nr == 0)
{
if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
sdl->setMonitorIds(ids);
else
{
sdl->setMonitorIds({ ids.front() });
}
}
else
{
/* There were more IDs supplied than there are monitors */
if (nr > ids.size())
{
WLog_ERR(TAG,
"Found %" PRIu32 " monitor IDs, but only have %" PRIuz " monitors connected",
nr, ids.size());
return FALSE;
}
std::vector<SDL_DisplayID> 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);
SDL_DisplayID id = *cur;
/* the ID is no valid monitor index */
if (std::find(ids.begin(), ids.end(), id) == ids.end())
{
WLog_ERR(TAG, "Supplied monitor ID[%" PRIuz "]=%" PRIu32 " is invalid", x, id);
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(id);
}
sdl->setMonitorIds(used);
}
if (!sdl_apply_display_properties(sdl))
return FALSE;
auto size = static_cast<uint32_t>(sdl->monitorIds().size());
if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, size))
return FALSE;
return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight);
}

View File

@@ -0,0 +1,28 @@
/**
* 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"
[[nodiscard]] int sdl_list_monitors(SdlContext* sdl);
[[nodiscard]] BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* ppMaxHeight);

View File

@@ -0,0 +1,254 @@
/**
* 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_context.hpp"
#include "sdl_touch.hpp"
#include "sdl_utils.hpp"
#include <SDL3/SDL_mouse.h>
typedef struct
{
rdpPointer pointer;
SDL_Cursor* cursor;
SDL_Surface* image;
size_t size;
void* data;
} sdlPointer;
[[nodiscard]] 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_DestroyCursor(ptr->cursor);
SDL_DestroySurface(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;
}
}
[[nodiscard]] static BOOL sdl_Pointer_SetDefault(rdpContext* context)
{
WINPR_UNUSED(context);
return sdl_push_user_event(SDL_EVENT_USER_POINTER_DEFAULT);
}
[[nodiscard]] static BOOL sdl_Pointer_Set(rdpContext* context, rdpPointer* pointer)
{
WINPR_UNUSED(context);
return sdl_push_user_event(SDL_EVENT_USER_POINTER_SET, pointer);
}
bool sdl_Pointer_Set_Process(SdlContext* sdl)
{
WINPR_ASSERT(sdl);
auto context = sdl->context();
auto pointer = sdl->cursor();
auto ptr = reinterpret_cast<sdlPointer*>(pointer);
if (!ptr)
return true;
rdpGdi* gdi = context->gdi;
WINPR_ASSERT(gdi);
auto ix = static_cast<float>(pointer->xPos);
auto iy = static_cast<float>(pointer->yPos);
auto isw = static_cast<float>(pointer->width);
auto ish = static_cast<float>(pointer->height);
auto window = SDL_GetMouseFocus();
if (!window)
return sdl_Pointer_SetDefault(context);
const Uint32 id = SDL_GetWindowID(window);
const SDL_FRect orig{ ix, iy, isw, ish };
auto pos = sdl->pixelToScreen(id, orig);
WLog_Print(sdl->getWLog(), WLOG_DEBUG, "cursor scale: pixel:%s, display:%s",
sdl::utils::toString(orig).c_str(), sdl::utils::toString(pos).c_str());
sdl_Pointer_Clear(ptr);
ptr->image =
SDL_CreateSurface(static_cast<int>(orig.w), static_cast<int>(orig.h), sdl->pixelFormat());
if (!ptr->image)
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "SDL_CreateSurface failed");
return false;
}
if (!SDL_LockSurface(ptr->image))
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "SDL_LockSurface failed");
return false;
}
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>(isw), static_cast<UINT32>(ish));
SDL_UnlockSurface(ptr->image);
if (!rc)
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "freerdp_image_scale failed");
return false;
}
// create a cursor image in 100% display scale to trick SDL into creating the cursor with the
// correct size
auto fw = sdl->getFirstWindow();
if (!fw)
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "sdl->getFirstWindow() nullptr");
return false;
}
const auto hidpi_scale =
sdl->pixelToScreen(fw->id(), SDL_FPoint{ static_cast<float>(ptr->image->w),
static_cast<float>(ptr->image->h) });
std::unique_ptr<SDL_Surface, void (*)(SDL_Surface*)> normal{
SDL_CreateSurface(static_cast<int>(hidpi_scale.x), static_cast<int>(hidpi_scale.y),
ptr->image->format),
SDL_DestroySurface
};
assert(normal);
if (!SDL_BlitSurfaceScaled(ptr->image, nullptr, normal.get(), nullptr,
SDL_ScaleMode::SDL_SCALEMODE_LINEAR))
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "SDL_BlitSurfaceScaled failed");
return false;
}
if (!SDL_AddSurfaceAlternateImage(normal.get(), ptr->image))
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "SDL_AddSurfaceAlternateImage failed");
return false;
}
ptr->cursor =
SDL_CreateColorCursor(normal.get(), static_cast<int>(pos.x), static_cast<int>(pos.y));
if (!ptr->cursor)
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "SDL_CreateColorCursor(display:%s, pixel:%s} failed",
sdl::utils::toString(pos).c_str(), sdl::utils::toString(orig).c_str());
return false;
}
if (!SDL_SetCursor(ptr->cursor))
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "SDL_SetCursor failed");
return false;
}
if (!SDL_ShowCursor())
{
WLog_Print(sdl->getWLog(), WLOG_ERROR, "SDL_ShowCursor failed");
return false;
}
sdl->setHasCursor(true);
return true;
}
[[nodiscard]] static BOOL sdl_Pointer_SetNull(rdpContext* context)
{
WINPR_UNUSED(context);
return sdl_push_user_event(SDL_EVENT_USER_POINTER_NULL);
}
[[nodiscard]] static BOOL sdl_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
{
WINPR_UNUSED(context);
WINPR_ASSERT(context);
return sdl_push_user_event(SDL_EVENT_USER_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,30 @@
/**
* 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 <SDL3/SDL.h>
#include "sdl_types.hpp"
#include <freerdp/graphics.h>
[[nodiscard]] bool sdl_register_pointer(rdpGraphics* graphics);
[[nodiscard]] bool sdl_Pointer_Set_Process(SdlContext* sdl);

View File

@@ -0,0 +1,208 @@
/**
* 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_context.hpp"
#include <winpr/wtypes.h>
#include <winpr/assert.h>
#include <freerdp/freerdp.h>
#include <freerdp/gdi/gdi.h>
#include <SDL3/SDL.h>
[[nodiscard]] 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;
}
[[nodiscard]] 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 SdlTouch::touchUp(SdlContext* sdl, const SDL_TouchFingerEvent& ev)
{
WINPR_ASSERT(sdl);
return freerdp_client_handle_touch(sdl->common(), FREERDP_TOUCH_UP | FREERDP_TOUCH_HAS_PRESSURE,
static_cast<INT32>(ev.fingerID),
sdl_scale_pressure(ev.pressure), static_cast<Sint32>(ev.x),
static_cast<Sint32>(ev.y));
}
bool SdlTouch::touchCancel(SdlContext* sdl, const SDL_TouchFingerEvent& ev)
{
WINPR_ASSERT(sdl);
return freerdp_client_handle_touch(
sdl->common(), FREERDP_TOUCH_CANCEL | FREERDP_TOUCH_HAS_PRESSURE,
static_cast<INT32>(ev.fingerID), sdl_scale_pressure(ev.pressure), static_cast<Sint32>(ev.x),
static_cast<Sint32>(ev.y));
}
bool SdlTouch::touchDown(SdlContext* sdl, const SDL_TouchFingerEvent& ev)
{
WINPR_ASSERT(sdl);
return freerdp_client_handle_touch(
sdl->common(), FREERDP_TOUCH_DOWN | FREERDP_TOUCH_HAS_PRESSURE,
static_cast<INT32>(ev.fingerID), sdl_scale_pressure(ev.pressure), static_cast<Sint32>(ev.x),
static_cast<Sint32>(ev.y));
}
bool SdlTouch::touchMotion(SdlContext* sdl, const SDL_TouchFingerEvent& ev)
{
WINPR_ASSERT(sdl);
return freerdp_client_handle_touch(
sdl->common(), FREERDP_TOUCH_MOTION | FREERDP_TOUCH_HAS_PRESSURE,
static_cast<INT32>(ev.fingerID), sdl_scale_pressure(ev.pressure), static_cast<Sint32>(ev.x),
static_cast<Sint32>(ev.y));
}
bool SdlTouch::handleEvent(SdlContext* sdl, const SDL_MouseMotionEvent& ev)
{
WINPR_ASSERT(sdl);
if (!sdl->getInputChannelContext().mouse_focus(ev.windowID))
return FALSE;
const BOOL relative =
freerdp_client_use_relative_mouse_events(sdl->common()) && !sdl->hasCursor();
auto x = static_cast<INT32>(relative ? ev.xrel : ev.x);
auto y = static_cast<INT32>(relative ? ev.yrel : ev.y);
return freerdp_client_send_button_event(sdl->common(), relative, PTR_FLAGS_MOVE, x, y);
}
bool SdlTouch::handleEvent(SdlContext* sdl, const SDL_MouseWheelEvent& ev)
{
WINPR_ASSERT(sdl);
const BOOL flipped = (ev.direction == SDL_MOUSEWHEEL_FLIPPED);
const auto x = static_cast<INT32>(ev.x * (flipped ? -1.0f : 1.0f) * 120.0f);
const auto y = static_cast<INT32>(ev.y * (flipped ? -1.0f : 1.0f) * 120.0f);
UINT16 flags = 0;
if (y != 0)
{
flags |= PTR_FLAGS_WHEEL;
if (!send_mouse_wheel(sdl, flags, y))
return false;
}
if (x != 0)
{
flags |= PTR_FLAGS_HWHEEL;
if (!send_mouse_wheel(sdl, flags, x))
return false;
}
return TRUE;
}
bool SdlTouch::handleEvent(SdlContext* sdl, const SDL_MouseButtonEvent& ev)
{
UINT16 flags = 0;
UINT16 xflags = 0;
WINPR_ASSERT(sdl);
if (ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
{
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()) && !sdl->hasCursor();
auto x = static_cast<INT32>(relative ? 0 : ev.x);
auto y = static_cast<INT32>(relative ? 0 : ev.y);
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;
}
bool SdlTouch::handleEvent(SdlContext* sdl, const SDL_TouchFingerEvent& ev)
{
switch (ev.type)
{
case SDL_EVENT_FINGER_CANCELED:
return SdlTouch::touchCancel(sdl, ev);
case SDL_EVENT_FINGER_UP:
return SdlTouch::touchUp(sdl, ev);
case SDL_EVENT_FINGER_DOWN:
return SdlTouch::touchDown(sdl, ev);
case SDL_EVENT_FINGER_MOTION:
return SdlTouch::touchMotion(sdl, ev);
default:
return false;
}
}

View File

@@ -0,0 +1,41 @@
/**
* 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 <SDL3/SDL.h>
#include "sdl_types.hpp"
class SdlTouch
{
public:
[[nodiscard]] static bool handleEvent(SdlContext* sdl, const SDL_MouseMotionEvent& ev);
[[nodiscard]] static bool handleEvent(SdlContext* sdl, const SDL_MouseWheelEvent& ev);
[[nodiscard]] static bool handleEvent(SdlContext* sdl, const SDL_MouseButtonEvent& ev);
[[nodiscard]] static bool handleEvent(SdlContext* sdl, const SDL_TouchFingerEvent& ev);
private:
[[nodiscard]] static bool touchDown(SdlContext* sdl, const SDL_TouchFingerEvent& ev);
[[nodiscard]] static bool touchUp(SdlContext* sdl, const SDL_TouchFingerEvent& ev);
[[nodiscard]] static bool touchCancel(SdlContext* sdl, const SDL_TouchFingerEvent& ev);
[[nodiscard]] static bool touchMotion(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;
[[nodiscard]] static inline SdlContext* get_context(void* ctx)
{
if (!ctx)
return nullptr;
auto sdl = static_cast<sdl_rdp_context*>(ctx);
return sdl->sdl;
}
[[nodiscard]] 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,610 @@
/**
* 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 <sstream>
#include <iomanip>
#include <random>
#include "sdl_utils.hpp"
#include "sdl_freerdp.hpp"
#include <SDL3/SDL.h>
#include <freerdp/version.h>
#include <freerdp/utils/string.h>
#define STR(x) #x
#define EV_CASE_STR(x) \
case x: \
return STR(x)
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_EVENT_USER_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_EVENT_USER_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_EVENT_USER_SCARD_DIALOG:
{
event->data1 = va_arg(ap, char*);
event->data2 = va_arg(ap, char**);
event->code = va_arg(ap, Sint32);
}
break;
case SDL_EVENT_USER_RETRY_DIALOG:
event->code = va_arg(ap, Sint32);
break;
case SDL_EVENT_USER_SCARD_RESULT:
case SDL_EVENT_USER_SHOW_RESULT:
case SDL_EVENT_USER_CERT_RESULT:
event->code = va_arg(ap, Sint32);
break;
case SDL_EVENT_USER_SHOW_DIALOG:
event->data1 = va_arg(ap, char*);
event->data2 = va_arg(ap, char*);
event->code = va_arg(ap, Sint32);
break;
case SDL_EVENT_USER_CERT_DIALOG:
event->data1 = va_arg(ap, char*);
event->data2 = va_arg(ap, char*);
break;
case SDL_EVENT_USER_UPDATE:
event->data1 = va_arg(ap, void*);
break;
case SDL_EVENT_USER_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_EVENT_USER_POINTER_SET:
event->data1 = va_arg(ap, void*);
event->data2 = va_arg(ap, void*);
break;
case SDL_EVENT_USER_CREATE_WINDOWS:
event->data1 = va_arg(ap, void*);
break;
case SDL_EVENT_USER_WINDOW_FULLSCREEN:
event->data1 = va_arg(ap, void*);
event->code = va_arg(ap, int);
event->data2 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, int)));
break;
case SDL_EVENT_USER_WINDOW_RESIZEABLE:
event->data1 = va_arg(ap, void*);
event->code = va_arg(ap, int);
break;
case SDL_EVENT_USER_WINDOW_MINIMIZE:
case SDL_EVENT_USER_QUIT:
case SDL_EVENT_USER_POINTER_NULL:
case SDL_EVENT_USER_POINTER_DEFAULT:
case SDL_EVENT_CLIPBOARD_UPDATE:
break;
default:
va_end(ap);
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] unsupported type %u", __func__, type);
return false;
}
va_end(ap);
const auto rc = SDL_PushEvent(&ev);
if (rc != 1)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] SDL_PushEvent returned %d", __func__, rc);
return rc == 1;
}
bool sdl_push_quit()
{
SDL_Event ev = {};
ev.type = SDL_EVENT_QUIT;
SDL_PushEvent(&ev);
return true;
}
namespace sdl::utils
{
UINT32 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;
}
}
std::string touchFlagsToString(Uint32 flags)
{
std::stringstream ss;
ss << "{";
bool first = true;
for (size_t x = 0; x < 32; x++)
{
const Uint32 mask = 1u << x;
if (flags & mask)
{
if (!first)
ss << "|";
first = false;
ss << freerdp_input_touch_state_string(mask);
}
}
ss << "}";
return ss.str();
}
std::string toString(SDL_DisplayOrientation orientation)
{
switch (orientation)
{
case SDL_ORIENTATION_LANDSCAPE:
return "SDL_ORIENTATION_LANDSCAPE";
case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
return "SDL_ORIENTATION_LANDSCAPE_FLIPPED";
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
return "SDL_ORIENTATION_PORTRAIT_FLIPPED";
case SDL_ORIENTATION_PORTRAIT:
return "SDL_ORIENTATION_PORTRAIT";
default:
{
std::stringstream ss;
ss << "SDL_ORIENTATION_UNKNOWN[0x" << std::hex << std::setw(8) << std::setfill('0')
<< orientation << "]";
return ss.str();
}
}
}
std::string toString(FreeRDP_DesktopRotationFlags orientation)
{
return freerdp_desktop_rotation_flags_to_string(orientation);
}
std::string toString(const SDL_DisplayMode* mode)
{
if (!mode)
return "SDL_DisplayMode=null";
std::stringstream ss;
ss << "["
<< "id=" << mode->displayID << ","
<< "fmt=" << mode->format << ","
<< "w=" << mode->w << ","
<< "h=" << mode->h << ","
<< "dpi=" << mode->pixel_density << ","
<< "refresh=" << mode->refresh_rate << ","
<< "num=" << mode->refresh_rate_numerator << ","
<< "denom=" << mode->refresh_rate_denominator << "]";
return ss.str();
}
std::string toString(Uint32 type)
{
switch (type)
{
EV_CASE_STR(SDL_EVENT_FIRST);
EV_CASE_STR(SDL_EVENT_QUIT);
EV_CASE_STR(SDL_EVENT_TERMINATING);
EV_CASE_STR(SDL_EVENT_LOW_MEMORY);
EV_CASE_STR(SDL_EVENT_WILL_ENTER_BACKGROUND);
EV_CASE_STR(SDL_EVENT_DID_ENTER_BACKGROUND);
EV_CASE_STR(SDL_EVENT_WILL_ENTER_FOREGROUND);
EV_CASE_STR(SDL_EVENT_DID_ENTER_FOREGROUND);
EV_CASE_STR(SDL_EVENT_LOCALE_CHANGED);
EV_CASE_STR(SDL_EVENT_SYSTEM_THEME_CHANGED);
EV_CASE_STR(SDL_EVENT_DISPLAY_ORIENTATION);
EV_CASE_STR(SDL_EVENT_DISPLAY_ADDED);
EV_CASE_STR(SDL_EVENT_DISPLAY_REMOVED);
EV_CASE_STR(SDL_EVENT_DISPLAY_MOVED);
EV_CASE_STR(SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED);
EV_CASE_STR(SDL_EVENT_WINDOW_SHOWN);
EV_CASE_STR(SDL_EVENT_WINDOW_HIDDEN);
EV_CASE_STR(SDL_EVENT_WINDOW_EXPOSED);
EV_CASE_STR(SDL_EVENT_WINDOW_MOVED);
EV_CASE_STR(SDL_EVENT_WINDOW_RESIZED);
EV_CASE_STR(SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED);
EV_CASE_STR(SDL_EVENT_WINDOW_MINIMIZED);
EV_CASE_STR(SDL_EVENT_WINDOW_MAXIMIZED);
EV_CASE_STR(SDL_EVENT_WINDOW_RESTORED);
EV_CASE_STR(SDL_EVENT_WINDOW_MOUSE_ENTER);
EV_CASE_STR(SDL_EVENT_WINDOW_MOUSE_LEAVE);
EV_CASE_STR(SDL_EVENT_WINDOW_FOCUS_GAINED);
EV_CASE_STR(SDL_EVENT_WINDOW_FOCUS_LOST);
EV_CASE_STR(SDL_EVENT_WINDOW_CLOSE_REQUESTED);
EV_CASE_STR(SDL_EVENT_WINDOW_HIT_TEST);
EV_CASE_STR(SDL_EVENT_WINDOW_ICCPROF_CHANGED);
EV_CASE_STR(SDL_EVENT_WINDOW_DISPLAY_CHANGED);
EV_CASE_STR(SDL_EVENT_WINDOW_SAFE_AREA_CHANGED);
EV_CASE_STR(SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED);
EV_CASE_STR(SDL_EVENT_WINDOW_OCCLUDED);
EV_CASE_STR(SDL_EVENT_WINDOW_ENTER_FULLSCREEN);
EV_CASE_STR(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN);
EV_CASE_STR(SDL_EVENT_WINDOW_DESTROYED);
EV_CASE_STR(SDL_EVENT_KEY_DOWN);
EV_CASE_STR(SDL_EVENT_KEY_UP);
EV_CASE_STR(SDL_EVENT_TEXT_EDITING);
EV_CASE_STR(SDL_EVENT_TEXT_INPUT);
EV_CASE_STR(SDL_EVENT_KEYMAP_CHANGED);
EV_CASE_STR(SDL_EVENT_KEYBOARD_ADDED);
EV_CASE_STR(SDL_EVENT_KEYBOARD_REMOVED);
EV_CASE_STR(SDL_EVENT_MOUSE_MOTION);
EV_CASE_STR(SDL_EVENT_MOUSE_BUTTON_DOWN);
EV_CASE_STR(SDL_EVENT_MOUSE_BUTTON_UP);
EV_CASE_STR(SDL_EVENT_MOUSE_WHEEL);
EV_CASE_STR(SDL_EVENT_MOUSE_ADDED);
EV_CASE_STR(SDL_EVENT_MOUSE_REMOVED);
EV_CASE_STR(SDL_EVENT_JOYSTICK_AXIS_MOTION);
EV_CASE_STR(SDL_EVENT_JOYSTICK_BALL_MOTION);
EV_CASE_STR(SDL_EVENT_JOYSTICK_HAT_MOTION);
EV_CASE_STR(SDL_EVENT_JOYSTICK_BUTTON_DOWN);
EV_CASE_STR(SDL_EVENT_JOYSTICK_BUTTON_UP);
EV_CASE_STR(SDL_EVENT_JOYSTICK_ADDED);
EV_CASE_STR(SDL_EVENT_JOYSTICK_REMOVED);
EV_CASE_STR(SDL_EVENT_JOYSTICK_BATTERY_UPDATED);
EV_CASE_STR(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE);
EV_CASE_STR(SDL_EVENT_GAMEPAD_AXIS_MOTION);
EV_CASE_STR(SDL_EVENT_GAMEPAD_BUTTON_DOWN);
EV_CASE_STR(SDL_EVENT_GAMEPAD_BUTTON_UP);
EV_CASE_STR(SDL_EVENT_GAMEPAD_ADDED);
EV_CASE_STR(SDL_EVENT_GAMEPAD_REMOVED);
EV_CASE_STR(SDL_EVENT_GAMEPAD_REMAPPED);
EV_CASE_STR(SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN);
EV_CASE_STR(SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION);
EV_CASE_STR(SDL_EVENT_GAMEPAD_TOUCHPAD_UP);
EV_CASE_STR(SDL_EVENT_GAMEPAD_SENSOR_UPDATE);
EV_CASE_STR(SDL_EVENT_GAMEPAD_UPDATE_COMPLETE);
EV_CASE_STR(SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED);
EV_CASE_STR(SDL_EVENT_FINGER_DOWN);
EV_CASE_STR(SDL_EVENT_FINGER_UP);
EV_CASE_STR(SDL_EVENT_FINGER_MOTION);
EV_CASE_STR(SDL_EVENT_CLIPBOARD_UPDATE);
EV_CASE_STR(SDL_EVENT_DROP_FILE);
EV_CASE_STR(SDL_EVENT_DROP_TEXT);
EV_CASE_STR(SDL_EVENT_DROP_BEGIN);
EV_CASE_STR(SDL_EVENT_DROP_COMPLETE);
EV_CASE_STR(SDL_EVENT_DROP_POSITION);
EV_CASE_STR(SDL_EVENT_AUDIO_DEVICE_ADDED);
EV_CASE_STR(SDL_EVENT_AUDIO_DEVICE_REMOVED);
EV_CASE_STR(SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED);
EV_CASE_STR(SDL_EVENT_SENSOR_UPDATE);
EV_CASE_STR(SDL_EVENT_PEN_DOWN);
EV_CASE_STR(SDL_EVENT_PEN_UP);
EV_CASE_STR(SDL_EVENT_PEN_MOTION);
EV_CASE_STR(SDL_EVENT_PEN_BUTTON_DOWN);
EV_CASE_STR(SDL_EVENT_PEN_BUTTON_UP);
EV_CASE_STR(SDL_EVENT_CAMERA_DEVICE_ADDED);
EV_CASE_STR(SDL_EVENT_CAMERA_DEVICE_REMOVED);
EV_CASE_STR(SDL_EVENT_CAMERA_DEVICE_APPROVED);
EV_CASE_STR(SDL_EVENT_CAMERA_DEVICE_DENIED);
EV_CASE_STR(SDL_EVENT_RENDER_TARGETS_RESET);
EV_CASE_STR(SDL_EVENT_RENDER_DEVICE_RESET);
EV_CASE_STR(SDL_EVENT_POLL_SENTINEL);
EV_CASE_STR(SDL_EVENT_USER);
EV_CASE_STR(SDL_EVENT_USER_CERT_DIALOG);
EV_CASE_STR(SDL_EVENT_USER_CERT_RESULT);
EV_CASE_STR(SDL_EVENT_USER_SHOW_DIALOG);
EV_CASE_STR(SDL_EVENT_USER_SHOW_RESULT);
EV_CASE_STR(SDL_EVENT_USER_AUTH_DIALOG);
EV_CASE_STR(SDL_EVENT_USER_AUTH_RESULT);
EV_CASE_STR(SDL_EVENT_USER_SCARD_DIALOG);
EV_CASE_STR(SDL_EVENT_USER_RETRY_DIALOG);
EV_CASE_STR(SDL_EVENT_USER_SCARD_RESULT);
EV_CASE_STR(SDL_EVENT_USER_UPDATE);
EV_CASE_STR(SDL_EVENT_USER_CREATE_WINDOWS);
EV_CASE_STR(SDL_EVENT_USER_WINDOW_RESIZEABLE);
EV_CASE_STR(SDL_EVENT_USER_WINDOW_FULLSCREEN);
EV_CASE_STR(SDL_EVENT_USER_WINDOW_MINIMIZE);
EV_CASE_STR(SDL_EVENT_USER_POINTER_NULL);
EV_CASE_STR(SDL_EVENT_USER_POINTER_DEFAULT);
EV_CASE_STR(SDL_EVENT_USER_POINTER_POSITION);
EV_CASE_STR(SDL_EVENT_USER_POINTER_SET);
EV_CASE_STR(SDL_EVENT_USER_QUIT);
EV_CASE_STR(SDL_EVENT_LAST);
default:
{
std::stringstream ss;
ss << "SDL_UNKNOWNEVENT[0x" << std::hex << std::setw(8) << std::setfill('0') << type
<< "]";
return ss.str();
}
}
}
std::string generate_uuid_v4()
{
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<> dis(0, 255);
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(2);
for (int i = 0; i < 4; i++)
{
ss << dis(gen);
}
ss << "-";
for (int i = 0; i < 2; i++)
{
ss << dis(gen);
}
ss << "-";
for (int i = 0; i < 2; i++)
{
ss << dis(gen);
}
ss << "-";
for (int i = 0; i < 2; i++)
{
ss << dis(gen);
}
ss << "-";
for (int i = 0; i < 6; i++)
{
ss << dis(gen);
}
return ss.str();
}
HighDpiScaleMode platformScaleMode()
{
const auto platform = SDL_GetPlatform();
if (!platform)
return SCALE_MODE_INVALID;
if (strcmp("Windows", platform) == 0)
return SCALE_MODE_X11;
if (strcmp("macOS", platform) == 0)
return SCALE_MODE_WAYLAND;
if (strcmp("Linux", platform) == 0)
{
const auto driver = SDL_GetCurrentVideoDriver();
if (!driver)
return SCALE_MODE_WAYLAND;
if (strcmp("x11", driver) == 0)
return SCALE_MODE_X11;
if (strcmp("wayland", driver) == 0)
return SCALE_MODE_WAYLAND;
}
return SCALE_MODE_INVALID;
}
std::string windowTitle(const rdpSettings* settings)
{
const char* prefix = "FreeRDP:";
if (!settings)
return {};
const auto windowTitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
if (windowTitle)
return {};
const auto name = freerdp_settings_get_server_name(settings);
const auto port = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort);
const auto addPort = (port != 3389);
std::stringstream ss;
ss << prefix << " ";
if (!addPort)
ss << name;
else
ss << name << ":" << port;
return ss.str();
}
std::string toString(SDL_Rect rect)
{
std::stringstream ss;
ss << "SDL_Rect{" << rect.x << "x" << rect.y << "-" << rect.w << "x" << rect.h << "}";
return ss.str();
}
std::string toString(SDL_FRect rect)
{
std::stringstream ss;
ss << "SDL_Rect{" << rect.x << "x" << rect.y << "-" << rect.w << "x" << rect.h << "}";
return ss.str();
}
} // namespace sdl::utils
namespace sdl::error
{
struct sdl_exitCode_map_t
{
DWORD error;
int code;
const char* code_tag;
};
#define ENTRY(x, y) { x, y, #y }
static const struct sdl_exitCode_map_t sdl_exitCode_map[] = {
ENTRY(FREERDP_ERROR_SUCCESS, SUCCESS), ENTRY(FREERDP_ERROR_NONE, DISCONNECT),
ENTRY(FREERDP_ERROR_NONE, LOGOFF), ENTRY(FREERDP_ERROR_NONE, IDLE_TIMEOUT),
ENTRY(FREERDP_ERROR_NONE, LOGON_TIMEOUT), ENTRY(FREERDP_ERROR_NONE, CONN_REPLACED),
ENTRY(FREERDP_ERROR_NONE, OUT_OF_MEMORY), ENTRY(FREERDP_ERROR_NONE, CONN_DENIED),
ENTRY(FREERDP_ERROR_NONE, CONN_DENIED_FIPS), ENTRY(FREERDP_ERROR_NONE, USER_PRIVILEGES),
ENTRY(FREERDP_ERROR_NONE, FRESH_CREDENTIALS_REQUIRED),
ENTRY(ERRINFO_LOGOFF_BY_USER, DISCONNECT_BY_USER), ENTRY(FREERDP_ERROR_NONE, UNKNOWN),
/* section 16-31: license error set */
ENTRY(FREERDP_ERROR_NONE, LICENSE_INTERNAL),
ENTRY(FREERDP_ERROR_NONE, LICENSE_NO_LICENSE_SERVER),
ENTRY(FREERDP_ERROR_NONE, LICENSE_NO_LICENSE),
ENTRY(FREERDP_ERROR_NONE, LICENSE_BAD_CLIENT_MSG),
ENTRY(FREERDP_ERROR_NONE, LICENSE_HWID_DOESNT_MATCH),
ENTRY(FREERDP_ERROR_NONE, LICENSE_BAD_CLIENT),
ENTRY(FREERDP_ERROR_NONE, LICENSE_CANT_FINISH_PROTOCOL),
ENTRY(FREERDP_ERROR_NONE, LICENSE_CLIENT_ENDED_PROTOCOL),
ENTRY(FREERDP_ERROR_NONE, LICENSE_BAD_CLIENT_ENCRYPTION),
ENTRY(FREERDP_ERROR_NONE, LICENSE_CANT_UPGRADE),
ENTRY(FREERDP_ERROR_NONE, LICENSE_NO_REMOTE_CONNECTIONS),
ENTRY(FREERDP_ERROR_NONE, LICENSE_CANT_UPGRADE),
/* section 32-127: RDP protocol error set */
ENTRY(FREERDP_ERROR_NONE, RDP),
/* section 128-254: xfreerdp specific exit codes */
ENTRY(FREERDP_ERROR_NONE, PARSE_ARGUMENTS), ENTRY(FREERDP_ERROR_NONE, MEMORY),
ENTRY(FREERDP_ERROR_NONE, PROTOCOL), ENTRY(FREERDP_ERROR_NONE, CONN_FAILED),
ENTRY(FREERDP_ERROR_AUTHENTICATION_FAILED, AUTH_FAILURE),
ENTRY(FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, NEGO_FAILURE),
ENTRY(FREERDP_ERROR_CONNECT_LOGON_FAILURE, LOGON_FAILURE),
ENTRY(FREERDP_ERROR_CONNECT_TARGET_BOOTING, CONNECT_TARGET_BOOTING),
ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, ACCOUNT_LOCKED_OUT),
ENTRY(FREERDP_ERROR_PRE_CONNECT_FAILED, PRE_CONNECT_FAILED),
ENTRY(FREERDP_ERROR_CONNECT_UNDEFINED, CONNECT_UNDEFINED),
ENTRY(FREERDP_ERROR_POST_CONNECT_FAILED, POST_CONNECT_FAILED),
ENTRY(FREERDP_ERROR_DNS_ERROR, DNS_ERROR),
ENTRY(FREERDP_ERROR_DNS_NAME_NOT_FOUND, DNS_NAME_NOT_FOUND),
ENTRY(FREERDP_ERROR_CONNECT_FAILED, CONNECT_FAILED),
ENTRY(FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, MCS_CONNECT_INITIAL_ERROR),
ENTRY(FREERDP_ERROR_TLS_CONNECT_FAILED, TLS_CONNECT_FAILED),
ENTRY(FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, INSUFFICIENT_PRIVILEGES),
ENTRY(FREERDP_ERROR_CONNECT_CANCELLED, CONNECT_CANCELLED),
ENTRY(FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, CONNECT_TRANSPORT_FAILED),
ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, CONNECT_PASSWORD_EXPIRED),
ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, CONNECT_PASSWORD_MUST_CHANGE),
ENTRY(FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, CONNECT_KDC_UNREACHABLE),
ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, CONNECT_ACCOUNT_DISABLED),
ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED, CONNECT_PASSWORD_CERTAINLY_EXPIRED),
ENTRY(FREERDP_ERROR_CONNECT_CLIENT_REVOKED, CONNECT_CLIENT_REVOKED),
ENTRY(FREERDP_ERROR_CONNECT_WRONG_PASSWORD, CONNECT_WRONG_PASSWORD),
ENTRY(FREERDP_ERROR_CONNECT_ACCESS_DENIED, CONNECT_ACCESS_DENIED),
ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, CONNECT_ACCOUNT_RESTRICTION),
ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, CONNECT_ACCOUNT_EXPIRED),
ENTRY(FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, CONNECT_LOGON_TYPE_NOT_GRANTED),
ENTRY(FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS, CONNECT_NO_OR_MISSING_CREDENTIALS)
};
static const sdl_exitCode_map_t* mapEntryByCode(int exit_code)
{
for (const auto& x : sdl_exitCode_map)
{
auto cur = &x;
if (cur->code == exit_code)
return cur;
}
return nullptr;
}
static const sdl_exitCode_map_t* mapEntryByError(UINT32 error)
{
for (const auto& x : sdl_exitCode_map)
{
auto cur = &x;
if (cur->error == error)
return cur;
}
return nullptr;
}
int errorToExitCode(DWORD error)
{
auto entry = mapEntryByError(error);
if (entry)
return entry->code;
return CONN_FAILED;
}
const char* errorToExitCodeTag(UINT32 error)
{
auto entry = mapEntryByError(error);
if (entry)
return entry->code_tag;
return nullptr;
}
const char* exitCodeToTag(int code)
{
auto entry = mapEntryByCode(code);
if (entry)
return entry->code_tag;
return nullptr;
}
} // namespace sdl::error

View File

@@ -0,0 +1,184 @@
/**
* 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 <freerdp/settings.h>
#include <SDL3/SDL.h>
#include <string>
#include <vector>
#include <memory>
#include <functional>
#include <sdl_common_utils.hpp>
template <typename T> using deleted_unique_ptr = std::unique_ptr<T, std::function<void(T*)>>;
enum
{
SDL_EVENT_USER_UPDATE = SDL_EVENT_USER + 1,
SDL_EVENT_USER_CREATE_WINDOWS,
SDL_EVENT_USER_WINDOW_RESIZEABLE,
SDL_EVENT_USER_WINDOW_FULLSCREEN,
SDL_EVENT_USER_WINDOW_MINIMIZE,
SDL_EVENT_USER_POINTER_NULL,
SDL_EVENT_USER_POINTER_DEFAULT,
SDL_EVENT_USER_POINTER_POSITION,
SDL_EVENT_USER_POINTER_SET,
SDL_EVENT_USER_QUIT,
SDL_EVENT_USER_CERT_DIALOG,
SDL_EVENT_USER_SHOW_DIALOG,
SDL_EVENT_USER_AUTH_DIALOG,
SDL_EVENT_USER_SCARD_DIALOG,
SDL_EVENT_USER_RETRY_DIALOG,
SDL_EVENT_USER_CERT_RESULT,
SDL_EVENT_USER_SHOW_RESULT,
SDL_EVENT_USER_AUTH_RESULT,
SDL_EVENT_USER_SCARD_RESULT
};
typedef struct
{
Uint32 type;
Uint32 timestamp;
char* title;
char* user;
char* domain;
char* password;
Sint32 result;
} SDL_UserAuthArg;
[[nodiscard]] bool sdl_push_user_event(Uint32 type, ...);
[[nodiscard]] bool sdl_push_quit();
[[nodiscard]] const char* sdl_error_string(Sint32 res);
#define sdl_log_error(res, log, what) sdl_log_error_ex(res, log, what, __FILE__, __LINE__, __func__)
[[nodiscard]] BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file,
size_t line, const char* fkt);
namespace sdl::utils
{
[[nodiscard]] std::string touchFlagsToString(Uint32 flags);
[[nodiscard]] std::string toString(enum FreeRDP_DesktopRotationFlags orientation);
[[nodiscard]] std::string toString(SDL_DisplayOrientation orientation);
[[nodiscard]] std::string toString(const SDL_DisplayMode* mode);
[[nodiscard]] std::string toString(Uint32 type);
[[nodiscard]] std::string toString(SDL_Rect rect);
[[nodiscard]] std::string toString(SDL_FRect rect);
[[nodiscard]] UINT32 orientaion_to_rdp(SDL_DisplayOrientation orientation);
[[nodiscard]] std::string generate_uuid_v4();
enum HighDpiScaleMode
{
SCALE_MODE_INVALID,
SCALE_MODE_X11,
SCALE_MODE_WAYLAND
};
[[nodiscard]] HighDpiScaleMode platformScaleMode();
[[nodiscard]] std::string windowTitle(const rdpSettings* settings);
} // namespace sdl::utils
namespace sdl::error
{
enum EXIT_CODE
{
/* section 0-15: protocol-independent codes */
SUCCESS = 0,
DISCONNECT = 1,
LOGOFF = 2,
IDLE_TIMEOUT = 3,
LOGON_TIMEOUT = 4,
CONN_REPLACED = 5,
OUT_OF_MEMORY = 6,
CONN_DENIED = 7,
CONN_DENIED_FIPS = 8,
USER_PRIVILEGES = 9,
FRESH_CREDENTIALS_REQUIRED = 10,
DISCONNECT_BY_USER = 11,
/* section 16-31: license error set */
LICENSE_INTERNAL = 16,
LICENSE_NO_LICENSE_SERVER = 17,
LICENSE_NO_LICENSE = 18,
LICENSE_BAD_CLIENT_MSG = 19,
LICENSE_HWID_DOESNT_MATCH = 20,
LICENSE_BAD_CLIENT = 21,
LICENSE_CANT_FINISH_PROTOCOL = 22,
LICENSE_CLIENT_ENDED_PROTOCOL = 23,
LICENSE_BAD_CLIENT_ENCRYPTION = 24,
LICENSE_CANT_UPGRADE = 25,
LICENSE_NO_REMOTE_CONNECTIONS = 26,
/* section 32-127: RDP protocol error set */
RDP = 32,
/* section 128-254: xfreerdp specific exit codes */
PARSE_ARGUMENTS = 128,
MEMORY = 129,
PROTOCOL = 130,
CONN_FAILED = 131,
AUTH_FAILURE = 132,
NEGO_FAILURE = 133,
LOGON_FAILURE = 134,
ACCOUNT_LOCKED_OUT = 135,
PRE_CONNECT_FAILED = 136,
CONNECT_UNDEFINED = 137,
POST_CONNECT_FAILED = 138,
DNS_ERROR = 139,
DNS_NAME_NOT_FOUND = 140,
CONNECT_FAILED = 141,
MCS_CONNECT_INITIAL_ERROR = 142,
TLS_CONNECT_FAILED = 143,
INSUFFICIENT_PRIVILEGES = 144,
CONNECT_CANCELLED = 145,
CONNECT_TRANSPORT_FAILED = 147,
CONNECT_PASSWORD_EXPIRED = 148,
CONNECT_PASSWORD_MUST_CHANGE = 149,
CONNECT_KDC_UNREACHABLE = 150,
CONNECT_ACCOUNT_DISABLED = 151,
CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152,
CONNECT_CLIENT_REVOKED = 153,
CONNECT_WRONG_PASSWORD = 154,
CONNECT_ACCESS_DENIED = 155,
CONNECT_ACCOUNT_RESTRICTION = 156,
CONNECT_ACCOUNT_EXPIRED = 157,
CONNECT_LOGON_TYPE_NOT_GRANTED = 158,
CONNECT_NO_OR_MISSING_CREDENTIALS = 159,
CONNECT_TARGET_BOOTING = 160,
UNKNOWN = 255,
};
[[nodiscard]] int errorToExitCode(DWORD error);
[[nodiscard]] const char* errorToExitCodeTag(UINT32 error);
[[nodiscard]] const char* exitCodeToTag(int code);
} // namespace sdl::error

View File

@@ -0,0 +1,486 @@
/**
* 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 <limits>
#include <sstream>
#include <cmath>
#include "sdl_window.hpp"
#include "sdl_utils.hpp"
#include <freerdp/utils/string.h>
SdlWindow::SdlWindow(SDL_DisplayID id, const std::string& title, const SDL_Rect& rect,
[[maybe_unused]] Uint32 flags)
: _displayID(id)
{
auto props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str());
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, rect.x);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, rect.y);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, rect.w);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, rect.h);
if (flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
if (flags & SDL_WINDOW_FULLSCREEN)
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
if (flags & SDL_WINDOW_BORDERLESS)
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
_window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
auto sc = scale();
const int iscale = static_cast<int>(sc * 100.0f);
auto w = 100 * rect.w / iscale;
auto h = 100 * rect.h / iscale;
std::ignore = resize({ w, h });
SDL_SetHint(SDL_HINT_APP_NAME, "");
std::ignore = SDL_SyncWindow(_window);
_monitor = query(_window, id, true);
}
SdlWindow::SdlWindow(SdlWindow&& other) noexcept
: _window(other._window), _displayID(other._displayID), _offset_x(other._offset_x),
_offset_y(other._offset_y), _monitor(other._monitor)
{
other._window = nullptr;
}
SdlWindow::~SdlWindow()
{
SDL_DestroyWindow(_window);
}
SDL_WindowID SdlWindow::id() const
{
if (!_window)
return 0;
return SDL_GetWindowID(_window);
}
SDL_DisplayID SdlWindow::displayIndex() const
{
if (!_window)
return 0;
return SDL_GetDisplayForWindow(_window);
}
SDL_Rect SdlWindow::rect() const
{
return rect(_window);
}
SDL_Rect SdlWindow::bounds() const
{
SDL_Rect rect = {};
if (_window)
{
if (!SDL_GetWindowPosition(_window, &rect.x, &rect.y))
return {};
if (!SDL_GetWindowSize(_window, &rect.w, &rect.h))
return {};
}
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;
}
rdpMonitor SdlWindow::monitor(bool isPrimary) const
{
auto m = _monitor;
if (isPrimary)
{
m.x = 0;
m.y = 0;
}
return m;
}
void SdlWindow::setMonitor(rdpMonitor monitor)
{
_monitor = monitor;
}
float SdlWindow::scale() const
{
return SDL_GetWindowDisplayScale(_window);
}
SDL_DisplayOrientation SdlWindow::orientation() const
{
const auto did = displayIndex();
return SDL_GetCurrentDisplayOrientation(did);
}
bool SdlWindow::grabKeyboard(bool enable)
{
if (!_window)
return false;
SDL_SetWindowKeyboardGrab(_window, enable);
return true;
}
bool SdlWindow::grabMouse(bool enable)
{
if (!_window)
return false;
SDL_SetWindowMouseGrab(_window, enable);
return true;
}
void SdlWindow::setBordered(bool bordered)
{
if (_window)
SDL_SetWindowBordered(_window, bordered);
std::ignore = SDL_SyncWindow(_window);
}
void SdlWindow::raise()
{
SDL_RaiseWindow(_window);
std::ignore = SDL_SyncWindow(_window);
}
void SdlWindow::resizeable(bool use)
{
SDL_SetWindowResizable(_window, use);
std::ignore = SDL_SyncWindow(_window);
}
void SdlWindow::fullscreen(bool enter, bool forceOriginalDisplay)
{
if (enter && forceOriginalDisplay && _displayID != 0)
{
/* Move the window to the desired display. We should not wait
* for the window to be moved, because some backends can refuse
* the move. The intent of moving the window is enough for SDL
* to decide which display will be used for fullscreen. */
SDL_Rect rect = {};
std::ignore = SDL_GetDisplayBounds(_displayID, &rect);
std::ignore = SDL_SetWindowPosition(_window, rect.x, rect.y);
}
std::ignore = SDL_SetWindowFullscreen(_window, enter);
std::ignore = SDL_SyncWindow(_window);
}
void SdlWindow::minimize()
{
SDL_MinimizeWindow(_window);
std::ignore = SDL_SyncWindow(_window);
}
bool SdlWindow::resize(const SDL_Point& size)
{
return SDL_SetWindowSize(_window, size.x, size.y);
}
bool SdlWindow::drawRect(SDL_Surface* surface, SDL_Point offset, const SDL_Rect& srcRect)
{
WINPR_ASSERT(surface);
SDL_Rect dstRect = { offset.x + srcRect.x, offset.y + srcRect.y, srcRect.w, srcRect.h };
return blit(surface, srcRect, dstRect);
}
bool SdlWindow::drawRects(SDL_Surface* surface, SDL_Point offset,
const std::vector<SDL_Rect>& rects)
{
if (rects.empty())
{
return drawRect(surface, offset, { 0, 0, surface->w, surface->h });
}
for (auto& srcRect : rects)
{
if (!drawRect(surface, offset, srcRect))
return false;
}
return true;
}
bool SdlWindow::drawScaledRect(SDL_Surface* surface, const SDL_FPoint& scale,
const SDL_Rect& srcRect)
{
SDL_Rect dstRect = srcRect;
dstRect.x = static_cast<Sint32>(static_cast<float>(dstRect.x) * scale.x);
dstRect.w = static_cast<Sint32>(static_cast<float>(dstRect.w) * scale.x);
dstRect.y = static_cast<Sint32>(static_cast<float>(dstRect.y) * scale.y);
dstRect.h = static_cast<Sint32>(static_cast<float>(dstRect.h) * scale.y);
return blit(surface, srcRect, dstRect);
}
bool SdlWindow::drawScaledRects(SDL_Surface* surface, const SDL_FPoint& scale,
const std::vector<SDL_Rect>& rects)
{
if (rects.empty())
{
return drawScaledRect(surface, scale, { 0, 0, surface->w, surface->h });
}
for (const auto& srcRect : rects)
{
if (!drawScaledRect(surface, scale, srcRect))
return false;
}
return true;
}
bool SdlWindow::fill(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
{
return fill(_window, r, g, b, a);
}
bool SdlWindow::fill(SDL_Window* window, 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_MapSurfaceRGBA(surface, r, g, b, a);
return SDL_FillSurfaceRect(surface, &rect, color);
}
rdpMonitor SdlWindow::query(SDL_Window* window, SDL_DisplayID id, bool forceAsPrimary)
{
if (!window)
return {};
const auto& r = rect(window, forceAsPrimary);
const float factor = SDL_GetWindowDisplayScale(window);
const float dpi = std::roundf(factor * 100.0f);
WINPR_ASSERT(r.w > 0);
WINPR_ASSERT(r.h > 0);
const auto primary = SDL_GetPrimaryDisplay();
const auto orientation = SDL_GetCurrentDisplayOrientation(id);
const auto rdp_orientation = sdl::utils::orientaion_to_rdp(orientation);
rdpMonitor monitor{};
monitor.orig_screen = id;
monitor.x = r.x;
monitor.y = r.y;
monitor.width = r.w;
monitor.height = r.h;
monitor.is_primary = forceAsPrimary || (id == primary);
monitor.attributes.desktopScaleFactor = static_cast<UINT32>(dpi);
monitor.attributes.deviceScaleFactor = 100;
monitor.attributes.orientation = rdp_orientation;
monitor.attributes.physicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, r.w);
monitor.attributes.physicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, r.h);
const auto cat = SDL_LOG_CATEGORY_APPLICATION;
SDL_LogDebug(cat, "monitor.orig_screen %" PRIu32, monitor.orig_screen);
SDL_LogDebug(cat, "monitor.x %" PRId32, monitor.x);
SDL_LogDebug(cat, "monitor.y %" PRId32, monitor.y);
SDL_LogDebug(cat, "monitor.width %" PRId32, monitor.width);
SDL_LogDebug(cat, "monitor.height %" PRId32, monitor.height);
SDL_LogDebug(cat, "monitor.is_primary %" PRIu32, monitor.is_primary);
SDL_LogDebug(cat, "monitor.attributes.desktopScaleFactor %" PRIu32,
monitor.attributes.desktopScaleFactor);
SDL_LogDebug(cat, "monitor.attributes.deviceScaleFactor %" PRIu32,
monitor.attributes.deviceScaleFactor);
SDL_LogDebug(cat, "monitor.attributes.orientation %s",
freerdp_desktop_rotation_flags_to_string(monitor.attributes.orientation));
SDL_LogDebug(cat, "monitor.attributes.physicalWidth %" PRIu32,
monitor.attributes.physicalWidth);
SDL_LogDebug(cat, "monitor.attributes.physicalHeight %" PRIu32,
monitor.attributes.physicalHeight);
return monitor;
}
SDL_Rect SdlWindow::rect(SDL_Window* window, bool forceAsPrimary)
{
SDL_Rect rect = {};
if (!window)
return {};
if (!forceAsPrimary)
{
if (!SDL_GetWindowPosition(window, &rect.x, &rect.y))
return {};
}
if (!SDL_GetWindowSizeInPixels(window, &rect.w, &rect.h))
return {};
return rect;
}
SdlWindow::HighDPIMode SdlWindow::isHighDPIWindowsMode(SDL_Window* window)
{
if (!window)
return MODE_INVALID;
const auto id = SDL_GetDisplayForWindow(window);
if (id == 0)
return MODE_INVALID;
const auto cs = SDL_GetDisplayContentScale(id);
const auto ds = SDL_GetWindowDisplayScale(window);
const auto pd = SDL_GetWindowPixelDensity(window);
/* mac os x style, but no HighDPI display */
if ((cs == 1.0f) && (ds == 1.0f) && (pd == 1.0f))
return MODE_NONE;
/* mac os x style HighDPI */
if ((cs == 1.0f) && (ds > 1.0f) && (pd > 1.0f))
return MODE_MACOS;
/* rest is windows style */
return MODE_WINDOWS;
}
bool SdlWindow::blit(SDL_Surface* surface, const SDL_Rect& srcRect, SDL_Rect& dstRect)
{
auto screen = SDL_GetWindowSurface(_window);
if (!screen || !surface)
return false;
if (!SDL_SetSurfaceClipRect(surface, &srcRect))
return true;
if (!SDL_SetSurfaceClipRect(screen, &dstRect))
return true;
if (!SDL_BlitSurfaceScaled(surface, &srcRect, screen, &dstRect, SDL_SCALEMODE_LINEAR))
{
SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_BlitScaled: %s", SDL_GetError());
return false;
}
return true;
}
void SdlWindow::updateSurface()
{
SDL_UpdateWindowSurface(_window);
}
SdlWindow SdlWindow::create(SDL_DisplayID id, const std::string& title, Uint32 flags, Uint32 width,
Uint32 height)
{
flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
SDL_Rect rect = { static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id)),
static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id)), static_cast<int>(width),
static_cast<int>(height) };
if ((flags & SDL_WINDOW_FULLSCREEN) != 0)
{
std::ignore = SDL_GetDisplayBounds(id, &rect);
}
SdlWindow window{ id, title, rect, flags };
if ((flags & (SDL_WINDOW_FULLSCREEN)) != 0)
{
window.setOffsetX(rect.x);
window.setOffsetY(rect.y);
}
return window;
}
static SDL_Window* createDummy(SDL_DisplayID id)
{
const auto x = SDL_WINDOWPOS_CENTERED_DISPLAY(id);
const auto y = SDL_WINDOWPOS_CENTERED_DISPLAY(id);
const int w = 64;
const int h = 64;
auto props = SDL_CreateProperties();
std::stringstream ss;
ss << "SdlWindow::query(" << id << ")";
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, ss.str().c_str());
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN, false);
auto window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
return window;
}
rdpMonitor SdlWindow::query(SDL_DisplayID id, bool forceAsPrimary)
{
std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> window(createDummy(id), SDL_DestroyWindow);
if (!window)
return {};
std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)> renderer(
SDL_CreateRenderer(window.get(), nullptr), SDL_DestroyRenderer);
if (!SDL_SyncWindow(window.get()))
return {};
SDL_Event event{};
while (SDL_PollEvent(&event))
;
return query(window.get(), id, forceAsPrimary);
}
SDL_Rect SdlWindow::rect(SDL_DisplayID id, bool forceAsPrimary)
{
std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> window(createDummy(id), SDL_DestroyWindow);
if (!window)
return {};
std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)> renderer(
SDL_CreateRenderer(window.get(), nullptr), SDL_DestroyRenderer);
if (!SDL_SyncWindow(window.get()))
return {};
SDL_Event event{};
while (SDL_PollEvent(&event))
;
return rect(window.get(), forceAsPrimary);
}

View File

@@ -0,0 +1,110 @@
/**
* 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 <vector>
#include <SDL3/SDL.h>
#include <freerdp/settings_types.h>
class SdlWindow
{
public:
[[nodiscard]] static SdlWindow create(SDL_DisplayID id, const std::string& title, Uint32 flags,
Uint32 width = 0, Uint32 height = 0);
[[nodiscard]] static rdpMonitor query(SDL_DisplayID id, bool forceAsPrimary = false);
SdlWindow(SdlWindow&& other) noexcept;
SdlWindow(const SdlWindow& other) = delete;
virtual ~SdlWindow();
SdlWindow& operator=(const SdlWindow& other) = delete;
SdlWindow& operator=(SdlWindow&& other) = delete;
[[nodiscard]] SDL_WindowID id() const;
[[nodiscard]] SDL_DisplayID displayIndex() const;
[[nodiscard]] SDL_Rect rect() const;
[[nodiscard]] SDL_Rect bounds() const;
[[nodiscard]] SDL_Window* window() const;
[[nodiscard]] Sint32 offsetX() const;
void setOffsetX(Sint32 x);
void setOffsetY(Sint32 y);
[[nodiscard]] Sint32 offsetY() const;
[[nodiscard]] rdpMonitor monitor(bool isPrimary) const;
void setMonitor(rdpMonitor monitor);
[[nodiscard]] float scale() const;
[[nodiscard]] SDL_DisplayOrientation orientation() const;
[[nodiscard]] bool grabKeyboard(bool enable);
[[nodiscard]] bool grabMouse(bool enable);
void setBordered(bool bordered);
void raise();
void resizeable(bool use);
void fullscreen(bool enter, bool forceOriginalDisplay);
void minimize();
[[nodiscard]] bool resize(const SDL_Point& size);
[[nodiscard]] bool drawRect(SDL_Surface* surface, SDL_Point offset, const SDL_Rect& srcRect);
[[nodiscard]] bool drawRects(SDL_Surface* surface, SDL_Point offset,
const std::vector<SDL_Rect>& rects = {});
[[nodiscard]] bool drawScaledRect(SDL_Surface* surface, const SDL_FPoint& scale,
const SDL_Rect& srcRect);
[[nodiscard]] bool drawScaledRects(SDL_Surface* surface, const SDL_FPoint& scale,
const std::vector<SDL_Rect>& rects = {});
[[nodiscard]] bool fill(Uint8 r = 0x00, Uint8 g = 0x00, Uint8 b = 0x00, Uint8 a = 0xff);
[[nodiscard]] bool blit(SDL_Surface* surface, const SDL_Rect& src, SDL_Rect& dst);
void updateSurface();
protected:
SdlWindow(SDL_DisplayID id, const std::string& title, const SDL_Rect& rect, Uint32 flags);
[[nodiscard]] static bool fill(SDL_Window* window, Uint8 r = 0x00, Uint8 g = 0x00,
Uint8 b = 0x00, Uint8 a = 0xff);
[[nodiscard]] static rdpMonitor query(SDL_Window* window, SDL_DisplayID id,
bool forceAsPrimary = false);
[[nodiscard]] static SDL_Rect rect(SDL_Window* window, bool forceAsPrimary = false);
[[nodiscard]] static SDL_Rect rect(SDL_DisplayID id, bool forceAsPrimary = false);
enum HighDPIMode
{
MODE_INVALID,
MODE_NONE,
MODE_WINDOWS,
MODE_MACOS
};
[[nodiscard]] static enum HighDPIMode isHighDPIWindowsMode(SDL_Window* window);
private:
SDL_Window* _window = nullptr;
SDL_DisplayID _displayID = 0;
Sint32 _offset_x = 0;
Sint32 _offset_y = 0;
rdpMonitor _monitor{};
};